为什么mypy拒绝我的“混合联合”类型的声明?

Why does mypy reject my "mixed union" type declaration?

提问人:Kevin 提问时间:7/15/2020 更新时间:10/23/2020 访问量:7350

问:

解决 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。DictMapping

这些错误令我感到惊讶。据我所知,每个函数的类型注释应该简化为同一组类型:一个字典,其键是字符串,其值是浮点数/字符串/整数。那么为什么只有不兼容的类型呢?mypy 是否对两个联盟的存在感到困惑?g

Python 注解 键入 mypy

评论

0赞 MisterMiyagi 7/15/2020
类似情况:一个值(例如)满足也满足但不满足。{'a': 1}Dict[str, Union[int, str]]Union[str, Dict[str, Union[int, str]]]Union[Dict[int, str], Dict[str, Union[int, str]]]

答:

6赞 alex_noname 7/15/2020 #1

这是因为是不变的。它应该是不变的,因为它是可变的。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

如果你需要异构,你可以使用 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才能使用 TypedDictTypedDict

评论

0赞 MisterMiyagi 7/15/2020
MyPy 没有问题。 无效正是因为 的值具有多个可能的具体类型——它的行为与 和 没有什么不同。d = {'a': 1}d["Key"].upper()de: Union[str, int]e.upper()
0赞 alex_noname 7/15/2020
Mypy 在文字初始化的情况下不会抱怨,但如果使用显式类型或隐式推断的变量,那么它就会抱怨。
1赞 alex_noname 7/15/2020
我想说这是类型推理的一个特征。这是一种硬/软约束,当我们在简单的情况下进行文字赋值(初始化)时,mypy 使用软约束,但如果它需要在 Union 变体之间进行选择或进行类型化 var 赋值,它会使用硬约束。
5赞 MisterMiyagi 10/22/2020 #2

问题在于,联合成员资格被建模为子类型,但字典键/值需要精确的类型匹配。这是由 MyPy 对嵌套类型 () 强制执行的,但对直接替换 ( ) 的解释很松散。gh


该类型被建模为(虚拟)子类型关系。也就是说,被认为是 的子类型。UnionstrUnion[str, x]

  • 这与检查是否是“非此即彼”而不说哪个的方式相匹配。只要只使用公共特征,我们就可以使用值来代替 -union。issubbclass(a, (str, int))astrintstr(str, int)

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

  • 对于像 dict 这样的可变类型,键/值既是输入又是输出。任何替代品都必须使用能力较弱的输入或提供能力更强的输出——这是不可能的,因为输入和输出必须具有相同的功能。d[k] = vv = d[k]

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


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