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

问题描述 投票:0回答:2

在解决 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

 替换为 
Mapping
 可以消除错误,但为了解决这个问题,我们必须使用 Dict。)

这些错误让我感到惊讶。据我所知,每个函数的类型注释应该简化为同一组类型:一个字典,其键是字符串,其值是浮点数/字符串/整数。那么为什么只有

g

 具有不兼容的类型呢? mypy 是否因两个 Union 的存在而感到困惑?

python python-typing mypy
2个回答
7
投票
这是因为

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

意味着包含异构数据:每个项目都允许有不同的类型。

如果您需要异构

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安装typing_extensions才能使用TypedDict


5
投票
问题在于

Union

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


Union

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

    这与
  • issubbclass(a, (str, int))
     检查 
    a
     是否为“
    str
    int
    ”的方式相匹配,而无需说明是哪个。只要仅使用共同特征,我们就可以使用 
    str
     值代替 
    (str, int)
     并集。

Dict

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

    对于 dict 这样的可变类型,键/值都是
  • input d[k] = v
    output v = d[k]
    。任何替代品都必须使用“能力较差”的输入或提供“能力更强”的输出——这是不可能的,因为输入和输出必须具有相同的能力。
    组合使用 Dict[..., Union[str, ...]] 需要值与
  • Union[str, ...]
完全匹配 - (虚拟)子类型

str

 无效。因此,
{"a": "b"}
 被视为 
Dict[str, str]
,它不能替代 
Dict[str, Union[str, int]]
由于
逻辑上
联合遵循与常规类型稍微不同的行为,MyPy 对于

Union

有一些单独的代码路径。这主要集中在函数签名和重载,其中存在单独的联合数学。因此,某些平面类型替换可以实现更实用的

Union
匹配,就像 h 的情况一样。
	
© www.soinside.com 2019 - 2024. All rights reserved.