为什么 mypy 拒绝我的"mixed union"类型声明?



在对Python聊天中的一个半相关问题进行故障排除时,我在mypy中发现了一些我不理解的行为。

from typing import Union, List, Dict
def f(x: Union[
Dict[str, float],
Dict[str, str],
Dict[str, int],
]):
pass
f({"a": 1})     #passes
f({"a": "b"})   #passes
f({"a": 1.0})   #passes
def g(x: Union[
Dict[str, float],
Dict[str, Union[str, int]],
]):
pass
g({"a": 1})     #fails
g({"a": "b"})   #fails
g({"a": 1.0})   #passes
def h(x: Dict[str, Union[float, str, int]]):
pass
h({"a": 1})     #passes
h({"a": "b"})   #passes
h({"a": 1.0})   #passes

当我在这个脚本上执行mypy时,它只抱怨中间的函数g:

C:UsersKevinDesktop>mypy test.py
test.py:20: error: Argument 1 to "g" has incompatible type "Dict[str, int]"; expected "Union[Dict[str, float], Dict[str, Union[str, int]]]"
test.py:20: note: "Dict" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance
test.py:20: note: Consider using "Mapping" instead, which is covariant in the value type
test.py:21: error: Argument 1 to "g" has incompatible type "Dict[str, str]"; expected "Union[Dict[str, float], Dict[str, Union[str, int]]]"
test.py:21: note: "Dict" is invariant -- see http://mypy.readthedocs.io/en/latest/common_issues.html#variance
test.py:21: note: Consider using "Mapping" instead, which is covariant in the value type
Found 2 errors in 1 file (checked 1 source file)

(正如注释所暗示的,用Mapping替换Dict可以消除错误,但为了这个问题,我必须使用Dict。(

这些错误让我感到惊讶。据我所知,每个函数的类型注释应该简化为同一组类型:一个dict,它的键是字符串,值是float/string/ints。那么,为什么只有g具有不兼容的类型呢?mypy是不是被两个工会的存在弄糊涂了?

这是因为Dict是不变的。它应该是不变的,因为它是可变的。

Dict[str, int]不是Dict[str, Union[str, int]]的亚型(即使intUnion[int, str]的亚型(

如果你要做这样的事情怎么办:

d: Dict[str, Union[str, int]]
u: Dict[str, int]
d = u  # Mypy error: Incompatible type
d["Key"] = "value"
Mypy假设字典是同构的:它们只包含一种类型。与此相反,例如,Tuples意味着包含异构数据:每个项都允许有不同的类型。

如果您需要异构的Dict,您可以使用TypedDict,但只需要一组固定的字符串密钥:

from typing import List, TypedDict
Mytype = TypedDict('Mytype', {'x': str, 'a': List[str]})
s: Mytype = {"x": "y", "a": ["b"]}
s['a'].append('c')

注:

除非您使用Python 3.8或更新版本(其中TypedDict在标准库键入模块中可用(,否则您需要使用pip安装typeing_extensions以使用TypedDict

问题是Union成员身份被建模为子类型,但Dict键/值需要精确的类型匹配。这是由MyPy对嵌套类型(g(强制执行的,但对直接替换(h(进行了松散解释。


Union类型被建模为(虚拟(子类型关系。也就是说,str被认为是Union[str, x]的一个亚型。

  • 这与CCD_ 21如何检查CCD_;CCD_ 23或CCD_;不用说是哪一个。只要只使用公共特性,我们就可以使用str值来代替(str, int)并集

Dict类型在其键/值类型中是不变的。也就是说,键/值必须与声明的类型完全相同。

  • 对于dict这样的可变类型,键/值都是输入d[k] = v输出v = d[k]。任何替代品都必须使用能力较弱的输入或提供功能更强的

组合使用Dict[..., Union[str, ...]]需要值与Union[str, ...]完全匹配–(虚拟(子类型str无效。因此,{"a": "b"}被认为是不能替代Dict[str, Union[str, int]]Dict[str, str]


由于在逻辑上并集遵循与常规类型略有不同的行为,MyPy为Union提供了一些独立的代码路径。这主要集中在函数签名和重载上,对于它们存在单独的联合数学。因此,某些平面型替换允许更实际的Union匹配,就像h的情况一样。

最新更新