14

我正在使用 Python 3.6.1、mypy 和打字模块。我创建了两个自定义类型,FooBar,然后在我从函数返回的字典中使用它们。dict 被描述为映射到straUnion和。然后我想在一个函数中使用这个dict中的值,每个函数只命名一个参数:FooBar

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"

如何将Union其转换为其子类型之一?

4

2 回答 2

16

你必须使用cast()

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

从 PEP 484 的Casts部分:

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

没有办法拼出字典键的特定值对应的特定类型的值。您可能需要考虑返回一个命名元组,它可以按键输入:

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

现在类型提示器确切知道每个属性类型是什么:

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

或者您可以使用数据类

from dataclasses import dataclass

@dataclass
class FooBarData:
    foo: Foo
    bar: Bar

这使得添加可选属性以及控制其他行为(例如相等测试或排序)变得更加容易。

我更喜欢typing.TypedDict,它更适用于遗留代码库和(JSON)序列化。

于 2017-06-24T18:54:15.340 回答
2

尽管我认为强制转换可能是在您的情况下使用的正确选项,但我只想简单地提及一个可能适用于类似情况的附加选项,以完善以下内容:

实际上,可以使用新的、实验性的TypedDict功能更精确地键入您的 dict ,该功能可用于 mypy 的最新版本(如果您从 github 存储库克隆),并且可能在下一个 pypi 版本中可用。

为了使用 TypedDict,你需要mypy_extensions从 pypi 运行pip install mypy_extensions.

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

from mypy_extensions import TypedDict

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

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

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

from mypy_extensions import TypedDict

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

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

您还提到您的 dict 可以具有动态数量的元素。如果它真的是动态的,那么 TypedDict 将无济于事,原因与 NamedTuple 无济于事,但如果您的 TypedDict 最终将具有有限数量的元素,并且您只是逐步向其中添加项目而不是全部一次,您可以尝试使用非全部 TypedDicts,或者尝试构建混合必需项和非必需项的TypeDicts 。

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

于 2017-06-25T15:56:46.213 回答