I’m using Python 3.6.1, mypy, and the typing module. I created two custom types, Foo and Bar, and then used them in a dict I return from a function. The dict is described as mapping str to a Union of Foo and Bar. Then I want to use values from this dict in a function that names only one argument each:
from typing import Dict, Union, NewType
Foo = NewType("Foo", str)
Bar = NewType("Bar", int)
def get_data() -> Dict[str, Union[Foo, Bar]]:
return {"foo": Foo("one"), "bar": Bar(2)}
def process(foo_value: Foo, bar_value: Bar) -> None:
pass
d = get_data()
I tried using the values as-is:
process(d["foo"], d["bar"])
# typing-union.py:15: error: Argument 1 to "process" has incompatible type "Union[Foo, Bar]"; expected "Foo"
# typing-union.py:15: error: Argument 2 to "process" has incompatible type "Union[Foo, Bar]"; expected "Bar"
Or using the types:
process(Foo(d["foo"]), Bar(d["bar"]))
# typing-union.py:20: error: Argument 1 to "Foo" has incompatible type "Union[Foo, Bar]"; expected "str"
# typing-union.py:20: error: Argument 1 to "Bar" has incompatible type "Union[Foo, Bar]"; expected "int"
How do I cast the Union to one of its subtypes?
You'd have to use cast():
process(cast(Foo, d["foo"]), cast(Bar, d["bar"]))
From the Casts section of PEP 484:
Occasionally the type checker may need a different kind of hint: the programmer may know that an expression is of a more constrained type than a type checker may be able to infer.
There is no way to spell what specific types of value go with what specific value of a dictionary key. You may want to consider returning a named tuple instead, which can be typed per key:
from typing import Dict, Union, NewType, NamedTuple
Foo = NewType("Foo", str)
Bar = NewType("Bar", int)
class FooBarData(NamedTuple):
foo: Foo
bar: Bar
def get_data() -> FooBarData:
return FooBarData(foo=Foo("one"), bar=Bar(2))
Now the type hinter knows exactly what each attribute type is:
d = get_data()
process(d.foo, d.bar)
Or you could use a dataclass:
from dataclasses import dataclass
@dataclass
class FooBarData:
foo: Foo
bar: Bar
which makes it easier to add optional attributes as well as control other behaviour (such as equality testing or ordering).
I prefer either over typing.TypedDict, which is more meant to be used with legacy codebases and (JSON) serialisations.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With