我有以下类,它将
str
映射到其相应的 int
或 None
(如果没有这样的键)。我希望它成为 collections.abc.MutableMapping
的子类。真正的逻辑比self._record.get()
要复杂一点,但整个事情可以归结为:
from typing import Iterator
from collections.abc import MutableMapping
class FooMap(MutableMapping[str, int]):
_record: dict[str, int]
def __init__(self) -> None:
self._record = {}
def __contains__(self, key: str) -> bool:
return self[key] is not None
def __getitem__(self, key: str) -> int | None:
return self._record.get(key)
def __setitem__(self, key: str, value: int) -> None:
self._record[key] = value
def __delitem__(self, key: str) -> None:
raise TypeError('Keys cannot be deleted')
def __len__(self) -> int:
return len(self._record)
def __iter__(self) -> Iterator[str]:
return iter(self._record)
然而,
mypy
抱怨这两个__contains__()
:
main.py:12: error: Argument 1 of "__contains__" is incompatible with supertype "Mapping"; supertype defines the argument type as "object" [override]
main.py:12: note: This violates the Liskov substitution principle
main.py:12: note: See https://mypy.readthedocs.io/en/stable/common_issues.html#incompatible-overrides
main.py:12: error: Argument 1 of "__contains__" is incompatible with supertype "Container"; supertype defines the argument type as "object" [override]
...以及
__getitem__()
方法:
main.py:15: error: Return type "int | None" of "__getitem__" incompatible with return type "int" in supertype "Mapping" [override]
我得到后者:
__getitem__()
的 Mapping[str, int]
方法应该返回 int
,而不是 None
,但这不是我的用例。将 int
更改为 int | None
没有帮助,因为 __setitem__()
的第二个参数也需要相应更改。
前者更令人困惑:传递给
__contains__()
的第一个参数应该是 str
,因为我们正在谈论 Mapping[str, int]
。然而,根据它给我的链接,mypy 期望比
object
更通用的东西。我把它改成了key: object
,但是没有效果,因为__getitem__()
想要一个str
。
我知道我可以扔掉
MutableMapping
或添加注释来明确告诉 mypy 它不需要仔细检查一行,但我也不想这样做。
如何在保留我最初的用例的同时让 mypy 满意?我可以使用 mypy 1.4+ 和 Python 3.11+ 支持的任何功能。
.register()
一个类作为 ABC
的“虚拟子类”:
from collections.abc import MutableMapping
class FooMap:
...
MutableMapping.register(FooMap)
这还支持运行时类型检查:
print(isinstance(FooMap(), MutableMapping)) # True
即使我最终没有使用这个(现在改变已经太晚了),它通过了
mypy
并且非常适合我的目的。