如何在Python中将types.Union转换为其中一个子类型?

时间:2017-06-24 18:40:50

标签: python python-3.x type-hinting mypy

我正在使用Python 3.6.1,mypy和输入模块。我创建了两个自定义类型FooBar,然后在函数返回的dict中使用它们。 dict被描述为将str映射到UnionFoo的{​​{1}}。然后我想在一个只命名一个参数的函数中使用这个dict中的值:

Bar

我尝试使用原样:

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()

或使用类型:

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"

如何将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" 转换为其子类型之一?

2 个答案:

答案 0 :(得分:3)

您必须使用cast()

process(cast(Foo, d["foo"]), cast(Bar, d["bar"]))

来自PEP 484的 Casts 部分:

  

类型检查器有时可能需要不同类型的提示:程序员可能知道表达式的类型比类型检查器可能推断的更具约束性。

没有办法拼写具体类型的值与字典键的具体值有什么关系。您可能需要考虑返回named tuple,而不是键入每个键:

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))

现在,类型hinter知道完全每个属性类型是什么:

d = get_data()
process(d.foo, d.bar)

答案 1 :(得分:1)

虽然我认为演员阵容可能是您案例中使用的正确选项,但我只想简单地提及一个可能适用于类似场景的其他选项,以解决问题:

实际上可以使用新的实验性TypedDict功能更准确地输入您的词典,该功能是最新版本的mypy(如果您从github repo克隆的话)并且可能会在下一个pypi发布。

要使用TypedDict,您需要运行pip install mypy_extensions从pypi安装mypy_extensions

TypedDict允许您为dict中的每个项目分配单独的类型:

from mypy_extensions import TypedDict

Foo = NewType("Foo", str)
Bar = NewType("Bar", int)

FooBarData = TypedDict('FooBarData', {
    'foo': Foo,
    'bar': Bar,
})

您还可以在Python 3.6 +中使用基于类的语法定义FooBarData

from mypy_extensions import TypedDict

Foo = NewType("Foo", str)
Bar = NewType("Bar", int)

class FooBarData(TypedDict):
    foo: Foo
    bar: Bar

你还提到你的dict可以拥有动态数量的元素。如果它真的是动态的,那么TypedDict会因为NamedTuple没有帮助的原因而获得帮助,但是如果你的TypedDict最终会有一个有限的元素数量,而你只是逐步添加项目而不是一次性,您可以尝试使用non-total TypedDicts,或尝试构建mix required and non-required items的TypeDicts。

值得注意的是,与其他几乎所有类型不同,TypedDicts使用结构类型进行检查,而不是标称类型。这意味着,如果您定义一个名为完全无关的TypedDict,例如QuxData,其foobar字段的类型与FooBarData相同,那么{{1}实际上将是QuxData的有效子类型。这可能会带来一些有趣的可能性,但有点聪明。