typing.TypeVar
类允许指定可重用类型变量。使用 Python 3.12 / PEP 695,可以使用类型变量 A
定义一个类 B
/T
,如下所示:
class A[T]:
...
class B[T]:
...
之前,使用 Python 3.11,你会这样做:
from typing import TypeVar, Generic
T = TypeVar("T")
class A(Generic[T]):
...
class B(Generic[T]):
...
对于第一个示例,
T
是在类范围中定义的,因此它们彼此不相关。
与第二个“旧”示例有什么区别吗?或者:两个类A
和B
之间有什么联系吗?
从类型检查员的 POV 来看不存在这样的联系。
TypeVar
在类范围之外声明只是因为这样做很方便,并不意味着其用户之间有任何关系。
类型变量绑定在以下范围内:
Generic
(或参数化 Protocol
,或其他泛型类),则参数化其父类的类型变量将绑定到类作用域。此类型变量的所有出现都将解析为相同上下文中的相同类型。没有其他绑定发生。
但是,可能存在清晰的语义关系(这是我反对 PEP695 的论点之一 - 不幸的是,我输掉了这场战斗)。考虑
django-stubs
(声明的永久链接):
# __set__ value type
_ST = TypeVar("_ST", contravariant=True)
# __get__ return type
_GT = TypeVar("_GT", covariant=True)
class Field(RegisterLookupMixin, Generic[_ST, _GT]):
... # omitted (there are 150+ lines here in fact)
class IntegerField(Field[_ST, _GT]):
...
class PositiveIntegerRelDbTypeMixin:
...
class SmallIntegerField(IntegerField[_ST, _GT]): ...
class BigIntegerField(IntegerField[_ST, _GT]):
...
class PositiveIntegerField(PositiveIntegerRelDbTypeMixin, IntegerField[_ST, _GT]): ...
class PositiveSmallIntegerField(PositiveIntegerRelDbTypeMixin, SmallIntegerField[_ST, _GT]): ...
class PositiveBigIntegerField(PositiveIntegerRelDbTypeMixin, BigIntegerField[_ST, _GT]): ...
显然,这些字段不必解析为相同类型的变量。然而,
_ST
和_GT
具有语义,它们都是相同的。这不会影响类型检查,但会帮助那些阅读此代码的人。如果 _GT
和 _ST
是为每个类单独定义的,那么,除了不可读且令人不快的语法之外,我们还必须在每个类附近重复注释或记录模块文档字符串中的内容,并添加一个额外的查找步骤揭开它们的意义的神秘面纱。使用较长的形式,如 _SetterType
和 _GetterType
可以消除解释的需要,但也会使本来就很长的签名变得更长。此外,仍然有必要解释 Field
打字的工作原理(还有其他注意事项)。现在,它在 typevar 声明之后的文档字符串中进行了解释,但使用 PEP695 样式,它会被写得很远。