【问题标题】:How to cast a typing.Union to one of its subtypes in Python?如何在 Python 中将类型化.Union 转换为其子类型之一?
【发布时间】:2017-11-28 02:46:44
【问题描述】:

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

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

【问题讨论】:

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


    【解决方案1】:

    你必须使用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))
    

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

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

    或者你可以使用dataclass:

    from dataclasses import dataclass
    
    @dataclass
    class FooBarData:
        foo: Foo
        bar: Bar
    

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

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

    【讨论】:

    • 谢谢!命名元组不起作用,因为 (a) dict 在实际代码中是可变长度的,并且 (b) 我不喜欢命名元组语义(例如,混合点访问和基于项的访问)。跨度>
    • @ChrisWarrick:当然,如果你有一个异构的可变长度数据结构,命名元组不会削减它。
    【解决方案2】:

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

    实际上可以使用新的实验性TypedDict 功能更精确地键入您的 dict,该功能可用于 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+ 中使用基于类的语法定义 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,它还具有与FooBarData 相同类型的foobar 字段,那么QuxData 实际上将是@987654335 的有效子类型@。这可能会打开一些有趣的可能性,有点聪明。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-10-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-08-18
      相关资源
      最近更新 更多