为何嵌套MutableMapping与dict类型不兼容?
MutableMapping[str, MutableMapping[str, Any]]与dict[str, dict[str, Any]]类型不兼容? 核心原因:泛型参数的不变性
Python的collections.abc.MutableMapping是泛型抽象类,它的泛型参数属于不变类型(invariant)。也就是说,只有当两个MutableMapping的所有泛型参数完全一致时,类型才会兼容——哪怕某个参数是另一个参数的子类也不行。
在你的例子里:
- 函数
foo要求的参数类型是MutableMapping[str, MutableMapping[str, Any]],它的value类型是抽象的MutableMapping - 你传入的变量类型是
dict[str, dict[str, Any]],它的value类型是具体的dict(dict是MutableMapping的子类)
虽然dict是MutableMapping的子类,但因为MutableMapping的泛型参数是不变的,MutableMapping[str, MutableMapping[...]]无法接受dict[str, dict[...]]作为参数。
为什么mypy要阻止这种兼容?
这是为了保证类型安全。假设允许这种赋值,函数foo里可以执行以下操作:
from collections.abc import MutableMapping class CustomMapping(MutableMapping[str, Any]): def __getitem__(self, key: str) -> Any: ... def __setitem__(self, key: str, value: Any) -> None: ... def __delitem__(self, key: str) -> None: ... def __iter__(self) -> iter[str]: ... def __len__(self) -> int: ... def foo(value: MutableMapping[str, MutableMapping[str, Any]]) -> None: # 创建一个非dict的MutableMapping实例 custom_map = CustomMapping() value["new_key"] = custom_map # 这里会把非dict的映射塞进原本要求value是dict的变量里
这时候,原本的value是dict[str, dict[str, Any]],它的所有value都应该是dict类型,但函数里却塞进了CustomMapping实例,直接破坏了变量的类型约束,导致后续代码如果把value当作dict处理时可能出错。
解决办法
有几种方式可以解决这个类型不兼容的问题:
调整函数的参数类型
如果函数只需要处理value为dict的情况,可以把参数类型改成MutableMapping[str, dict[str, Any]],这样和传入的变量类型完全匹配:def foo(value: MutableMapping[str, dict[str, Any]]) -> None: print(value) value: dict[str, dict[str, Any]] = {} foo(value) # 不再报错调整变量的类型注解
把变量的类型注解改成MutableMapping[str, MutableMapping[str, Any]],因为dict是MutableMapping的子类,所以可以正常赋值,同时也符合函数的参数要求:from collections.abc import MutableMapping from typing import Any def foo(value: MutableMapping[str, MutableMapping[str, Any]]) -> None: print(value) value: MutableMapping[str, MutableMapping[str, Any]] = {} foo(value) # 不再报错使用只读的
Mapping类型(如果函数不修改映射)
如果函数只是读取映射的内容,不会修改它的结构或value,可以把参数类型改成Mapping[str, Mapping[str, Any]]。Mapping的泛型参数是**协变(covariant)**的,所以dict[str, dict[...]]可以兼容Mapping[str, Mapping[...]]:from collections.abc import Mapping from typing import Any def foo(value: Mapping[str, Mapping[str, Any]]) -> None: print(value) value: dict[str, dict[str, Any]] = {} foo(value) # 不再报错
内容的提问来源于stack exchange,提问作者Serik

