I have a type for a dictionary of variables passed to a template:
VariablesDict = Dict[str, Union[int, float, str, None]]
Basically, any dictionary where the keys are strings and the values are strings, numbers or None. I use this type in several template related functions.
Take this example function:
def render_template(name: str, variables: VariablesDict):
...
Calling this function with a dictionary literal works fine:
render_template("foo", {"key": "value"})
However, if I assign the dictionary to a variable first, like this:
variables = {"key": "value"}
render_template("foo", variables)
Mypy gives an error:
Argument 2 to "render_template" has incompatible type "Dict[str, str]"; expected "Dict[str, Union[int, float, str, None]]"
It seems to me that any value of type Dict[str, str]
should be safe to pass to a function that expects a parameter of type Dict[str, Union[int, float, str, None]]
. Why doesn't that work by default? Is there anything I can do to make this work?
The reason it doesn't work is that Dict
is mutable, and a function which accepts a Dict[str, int|float|str|None]
could therefore reasonably insert any of those types into its argument. If the argument was actually a Dict[str, str]
, it now contains values that violate its type. (For more on this, google "covariance/contravariance/invariance" and "Liskov Substitution Principle" -- as a general rule, mutable containers are invariant over their generic type[s].)
As long as render_template
doesn't need to modify the dict you pass to it, an easy fix is to have it take a Mapping
(which is an abstract supertype of dict
that doesn't imply mutability, and is therefore covariant) instead of a Dict
:
def render_template(name: str, variables: Mapping[str, Union[int, float, str, None]]):
...