如何将 typing.Union 转换为 Python 中的其中一个子类型?
How to cast a typing.Union to one of its subtypes in Python?
我正在使用 Python 3.6.1、mypy 和输入模块。我创建了两个自定义类型,Foo
和 Bar
,然后在一个函数的字典中使用它们 return。该字典被描述为将 str
映射到 Foo
和 Bar
的 Union
。然后我想在一个函数中使用这个字典中的值,每个函数只命名一个参数:
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
转换为其子类型之一?
你必须使用 cast()
:
process(cast(Foo, d["foo"]), cast(Bar, d["bar"]))
来自 PEP 484 的 Casts 部分:
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.
无法拼写特定类型的值与字典键的特定值对应。您可能需要考虑返回一个 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))
现在类型提示器确切知道每个属性类型是什么:
d = get_data()
process(d.foo, d.bar)
或者您可以使用 dataclass:
from dataclasses import dataclass
@dataclass
class FooBarData:
foo: Foo
bar: Bar
这使得添加可选属性以及控制其他行为(例如相等性测试或排序)变得更加容易。
比起 typing.TypedDict
,我更喜欢两者之一,后者更适合与遗留代码库和 (JSON) 序列化一起使用。
虽然我认为在您的情况下使用转换可能是正确的选择,但我只是想简单地提一下可能适用于类似场景的另一个选项,以解决问题:
实际上可以使用新的实验性 TypedDict 功能更精确地键入您的字典,该功能在最新版本的 mypy 中可用(如果您从 github 存储库克隆)并且很可能是在下一个 pypi 版本中可用。
为了使用 TypedDict,您需要通过 运行 pip install mypy_extensions
.
从 pypi 安装 mypy_extensions
TypedDict 允许您为字典中的每个项目分配单独的类型:
from mypy_extensions import TypedDict
Foo = NewType("Foo", str)
Bar = NewType("Bar", int)
FooBarData = TypedDict('FooBarData', {
'foo': Foo,
'bar': Bar,
})
您还可以在 Python 3.6+:
中使用基于 class 的语法定义 FooBarData
from mypy_extensions import TypedDict
Foo = NewType("Foo", str)
Bar = NewType("Bar", int)
class FooBarData(TypedDict):
foo: Foo
bar: Bar
您还提到您的字典可以包含动态数量的元素。如果它确实是动态的,那么 TypedDict 将无济于事,原因与 NamedTuple 无济于事的原因相同,但是如果您的 TypedDict 最终将具有有限数量的元素,并且您只是逐渐向其中添加项目而不是全部立即,您可以尝试使用 non-total TypedDicts, or try constructing TypeDicts that mix required and non-required items.
还值得注意的是,与几乎所有其他类型不同,TypedDicts 使用结构类型而不是名义类型进行检查。这意味着,如果您定义一个完全不相关的 TypedDict,例如 QuxData
,它也有 foo
和 bar
字段,其类型与 FooBarData
相同,那么 QuxData
实际上是 FooBarData
的有效子类型。稍微聪明一点,这可能会打开一些有趣的可能性。
我正在使用 Python 3.6.1、mypy 和输入模块。我创建了两个自定义类型,Foo
和 Bar
,然后在一个函数的字典中使用它们 return。该字典被描述为将 str
映射到 Foo
和 Bar
的 Union
。然后我想在一个函数中使用这个字典中的值,每个函数只命名一个参数:
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
转换为其子类型之一?
你必须使用 cast()
:
process(cast(Foo, d["foo"]), cast(Bar, d["bar"]))
来自 PEP 484 的 Casts 部分:
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.
无法拼写特定类型的值与字典键的特定值对应。您可能需要考虑返回一个 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))
现在类型提示器确切知道每个属性类型是什么:
d = get_data()
process(d.foo, d.bar)
或者您可以使用 dataclass:
from dataclasses import dataclass
@dataclass
class FooBarData:
foo: Foo
bar: Bar
这使得添加可选属性以及控制其他行为(例如相等性测试或排序)变得更加容易。
比起 typing.TypedDict
,我更喜欢两者之一,后者更适合与遗留代码库和 (JSON) 序列化一起使用。
虽然我认为在您的情况下使用转换可能是正确的选择,但我只是想简单地提一下可能适用于类似场景的另一个选项,以解决问题:
实际上可以使用新的实验性 TypedDict 功能更精确地键入您的字典,该功能在最新版本的 mypy 中可用(如果您从 github 存储库克隆)并且很可能是在下一个 pypi 版本中可用。
为了使用 TypedDict,您需要通过 运行 pip install mypy_extensions
.
mypy_extensions
TypedDict 允许您为字典中的每个项目分配单独的类型:
from mypy_extensions import TypedDict
Foo = NewType("Foo", str)
Bar = NewType("Bar", int)
FooBarData = TypedDict('FooBarData', {
'foo': Foo,
'bar': Bar,
})
您还可以在 Python 3.6+:
中使用基于 class 的语法定义FooBarData
from mypy_extensions import TypedDict
Foo = NewType("Foo", str)
Bar = NewType("Bar", int)
class FooBarData(TypedDict):
foo: Foo
bar: Bar
您还提到您的字典可以包含动态数量的元素。如果它确实是动态的,那么 TypedDict 将无济于事,原因与 NamedTuple 无济于事的原因相同,但是如果您的 TypedDict 最终将具有有限数量的元素,并且您只是逐渐向其中添加项目而不是全部立即,您可以尝试使用 non-total TypedDicts, or try constructing TypeDicts that mix required and non-required items.
还值得注意的是,与几乎所有其他类型不同,TypedDicts 使用结构类型而不是名义类型进行检查。这意味着,如果您定义一个完全不相关的 TypedDict,例如 QuxData
,它也有 foo
和 bar
字段,其类型与 FooBarData
相同,那么 QuxData
实际上是 FooBarData
的有效子类型。稍微聪明一点,这可能会打开一些有趣的可能性。