我有一个 Python 包,它基于 collections.abc 提供的 ABC(Mapping、Sequence 等)定义了各种集合。我想利用 Python 3.5 中引入的类型提示功能,但我怀疑什么是最好的方法。
让我们以其中一个类为例;直到现在,我还有一些东西 类似这样:
from collections.abc import Mapping
class MyMapping(Mapping):
...
要将其转换为泛型类型,文档建议执行以下操作:
from typing import TypeVar, Hashable, Mapping
K = TypeVar("K", bound=Hashable)
V = TypeVar("V")
class MyMapping(Mapping[K, V]):
...
但这带来了两个问题:
该类释放了 collections.abc.Mapping 中的所有 mixin 方法。我可以自己解决这个问题,但这会破坏使用 ABC 的部分目的。
isinstance(MyMapping(), collections.abc.Mapping)
返回 False。此外,尝试调用 collections.abc.Mapping.register(MyMapping)
来解决此问题会引发 RuntimeError(“拒绝创建继承循环”)。我解决这些问题的第一次尝试是回去扩展 collections.abc.Mapping:
from typing import TypeVar, Hashable
from collections.abc import Mapping
K = TypeVar("K", bound=Hashable)
V = TypeVar("V")
class MyMapping(Mapping[K, V]):
...
但这不起作用,因为 collections.abc.Mapping 不是泛型类型,并且不支持订阅运算符。所以我尝试了这个:
from typing import TypeVar, Hashable, Mapping
from collections.abc import Mapping as MappingABC
K = TypeVar("K", bound=Hashable)
V = TypeVar("V")
class MyMapping(MappingABC, Mapping[K, V]):
...
但这闻起来有鱼腥味。导入和别名很麻烦,生成的类有曲折的 MRO,ABC 提供的混合方法不会有类型信息...
那么基于集合 ABC 声明自定义泛型类型的首选方法是什么?
[个人意见™]:我不太支持创建新的
typing
功能。这些应该足够通用,不需要对代码进行任何修改。如果您的映射类非常复杂,无法用任何常见映射(如 dict
)替代,那么您最好只使用它自己:
def foo(bar: MyMapping) -> List:
pass
而不是
def foo(bar: Mapping[K, V]) -> List:
pass
现在,如果您希望用户能够使用
typing.Mapping
“输入”检查您的类,您只需要子类化 collections.Mapping
class MyMapping(collections.abc.Mapping):
... # define required methods
isinstance(MyMapping(), typing.Mapping[K, V]) # --> True
您的原始代码可以与当前版本的 python 和 mypy 配合良好,并且完全按照您想要的方式执行所有操作(包括重用
collections.abc.Mapping
中的实现)。
但是,暂时您应该删除
bound=Hashable
,因为它尚未完全支持:
from typing import TypeVar, Hashable, Mapping
K = TypeVar("K")
V = TypeVar("V")
class MyMapping(Mapping[K, V]):
...
现在
typing.Mapping
已被弃用,其功能已“转移”到 collections.abc.Mapping
,这将毫无问题地工作:
from collections.abc import Mapping, Hashable
from typing import TypeVar
K = TypeVar('K', bound = Hashable)
V = TypeVar('V')
class MyMapping(Mapping[K, V]):
...
自 Python 3.9 以来,通过 PEP 585,这已经成为可能。
或者,如果您更喜欢 PEP 695/Python 3.12+ 语法:
from collections.abc import Mapping, Hashable
class MyMapping[K: Hashable, V](Mapping[K, V]):
...