为何typing.MutableMapping无法作为Protocol基类?如何基于其定义自定义映射协议?
这个问题我之前也碰到过,核心原因是typing.MutableMapping和typing.Sized在mypy中的类型定位不同:typing.Sized已经是PEP 544兼容的Protocol类型,而typing.MutableMapping只是collections.abc.MutableMapping(一个传统抽象基类ABC)的别名,旧版mypy会严格要求Protocol的所有基类必须是Protocol本身,所以才会抛出那个错误。
下面给你两个可行的解决方案:
方案1:升级mypy并改用collections.abc.MutableMapping作为基类
如果你的mypy版本能升级到0.910及以上,它已经支持Protocol继承自ABC类型。你只需要把基类换成collections.abc.MutableMapping,就能自动继承所有MutableMapping的抽象方法要求,不用手动编写:
from collections.abc import MutableMapping from typing import Hashable, Protocol, TypeVar TVarMapProt = TypeVar("TVarMapProt", bound="VariableMappingProtocol") class VariableMappingProtocol(MutableMapping[Hashable, int], Protocol): """ Protocol for the type behaviour of a class which acts basically like a dict of int objects. """ def copy(self: TVarMapProt) -> TVarMapProt: # 要是你用的是Python 3.11+,可以直接替换成Self类型,更简洁: # from typing import Self # def copy(self) -> Self: ...
这样mypy会自动把MutableMapping的所有核心方法(比如__getitem__、__setitem__)纳入结构类型检查,同时你的自定义copy()方法也会被要求实现。
方案2:手动声明核心方法(兼容旧版mypy)
如果暂时无法升级mypy,也可以手动声明MutableMapping的核心方法——结构类型检查只需要这些关键方法,不用写全所有方法:
from typing import Hashable, Protocol, TypeVar, Iterator TVarMapProt = TypeVar("TVarMapProt", bound="VariableMappingProtocol") class VariableMappingProtocol(Protocol): """ Protocol for the type behaviour of a class which acts basically like a dict of int objects. """ def __getitem__(self, key: Hashable) -> int: ... def __setitem__(self, key: Hashable, value: int) -> None: ... def __delitem__(self, key: Hashable) -> None: ... def __iter__(self) -> Iterator[Hashable]: ... def __len__(self) -> int: ... def copy(self: TVarMapProt) -> TVarMapProt: ...
这种方式虽然多写了几行,但能完美兼容旧版mypy,同时保持Protocol的结构类型特性。
自定义类的正确实现方式
需要注意的是,你的自定义类不需要显式继承VariableMappingProtocol——只要实现了所有要求的方法(包括MutableMapping的核心方法和copy()),mypy就会自动识别它符合该Protocol:
from collections.abc import Iterator, Hashable from typing import TypeVar TCusVarMap = TypeVar("TCusVarMap", bound="CustomVariableMapping") class CustomVariableMapping: """Defines all the methods that a MutableMapping does, but mapping Hashables to ints""" def __getitem__(self, key: Hashable) -> int: # 替换成你的实现逻辑 return 0 def __setitem__(self, key: Hashable, value: int) -> None: # 替换成你的实现逻辑 pass def __delitem__(self, key: Hashable) -> None: # 替换成你的实现逻辑 pass def __iter__(self) -> Iterator[Hashable]: # 替换成你的实现逻辑 return iter([]) def __len__(self) -> int: # 替换成你的实现逻辑 return 0 def copy(self: TCusVarMap) -> TCusVarMap: # 替换成你的实现逻辑 return CustomVariableMapping()
如果用的是Python 3.11+,还可以用Self类型简化TypeVar的写法:
from typing import Self class CustomVariableMapping: # ... 其他方法保持不变 ... def copy(self) -> Self: return CustomVariableMapping()
内容的提问来源于stack exchange,提问作者ThomasNicholas

