提问人:Kevin 提问时间:7/15/2020 更新时间:10/23/2020 访问量:7350
为什么mypy拒绝我的“混合联合”类型的声明?
Why does mypy reject my "mixed union" type declaration?
问:
在解决 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:\Users\Kevin\Desktop>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)
(正如注释所暗示的那样,替换为可以消除错误,但为了这个问题,让我们说我必须使用 Dict。Dict
Mapping
这些错误令我感到惊讶。据我所知,每个函数的类型注释应该简化为同一组类型:一个字典,其键是字符串,其值是浮点数/字符串/整数。那么为什么只有不兼容的类型呢?mypy 是否对两个联盟的存在感到困惑?g
答:
这是因为是不变的。它应该是不变的,因为它是可变的。Dict
Dict[str, int]
不是 的子类型(即使 是Dict[str, Union[str, int]]
int
Union[int, str]
)
如果你要做这样的事情怎么办:
d: Dict[str, Union[str, int]]
u: Dict[str, int]
d = u # Mypy error: Incompatible type
d["Key"] = "value"
Mypy 假设词典是同质的:它们只会包含一种类型。与此相反,例如,旨在包含异构数据:允许每个项目具有不同的类型。Tuples
如果你需要异构,你可以使用 TypedDict
,但只需要一组固定的字符串键:Dict
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 或更高版本(在标准库类型模块中可用),否则您需要使用 pip 安装typing_extensions才能使用 TypedDict
TypedDict
评论
d = {'a': 1}
d["Key"].upper()
d
e: Union[str, int]
e.upper()
问题在于,联合
成员资格被建模为子类型,但字典
键/值需要精确的类型匹配。这是由 MyPy 对嵌套类型 () 强制执行的,但对直接替换 ( ) 的解释很松散。g
h
该类型被建模为(虚拟)子类型关系。也就是说,被认为是 的子类型。Union
str
Union[str, x]
- 这与检查是否是“非此即彼”而不说哪个的方式相匹配。只要只使用公共特征,我们就可以使用值来代替 -union。
issubbclass(a, (str, int))
a
str
int
str
(str, int)
该类型的键/值类型是不变的。也就是说,键/值必须与声明的类型完全相同。Dict
- 对于像 dict 这样的可变类型,键/值既是输入又是输出。任何替代品都必须使用能力较弱的输入或提供能力更强的输出——这是不可能的,因为输入和输出必须具有相同的功能。
d[k] = v
v = d[k]
在组合中,使用 a 需要值与(虚拟)子类型完全匹配 – 无效。因此,被认为是不能替代的。Dict[..., Union[str, ...]]
Union[str, ...]
str
{"a": "b"}
Dict[str, str]
Dict[str, Union[str, int]]
由于从逻辑上讲,联合遵循与常规类型略有不同的行为,因此 MyPy 为 Union
提供了一些单独的代码路径。这主要集中在函数签名和重载上,为此存在单独的联合数学。因此,某些平面类型替换允许更实际的匹配,如 。Union
h
评论
{'a': 1}
Dict[str, Union[int, str]]
Union[str, Dict[str, Union[int, str]]]
Union[Dict[int, str], Dict[str, Union[int, str]]]