The typing.TypeVar
class allows one to specify reusable type variables. With Python 3.12 / PEP 695, one can define a class A
/B
with type variable T
like this:
class A[T]:
...
class B[T]:
...
Beforehand, with Python 3.11, you would do it like this:
from typing import TypeVar, Generic
T = TypeVar("T")
class A(Generic[T]):
...
class B(Generic[T]):
...
For the first example, the T
is defined in the class scope, so they do not relate to each other.
Is there any difference to the second 'old' example? Or: Is there any connection between the two classes A
and B
?
There is no such connection from the typechecker's POV. TypeVar
is declared outside of class scope just because it's convenient to do so, it does not imply any relationships between its users.
Type variable is bound in the following scopes:
Generic
(or parametrized Protocol
, or other generic class), type variable that parametrizes its parents is bound to class scope. All occurrences of this type variable are resolved to the same type in same context.No other binding is taking place. This is explained in detail in PEP 484 (link to relevant section).
However, there may be clean semantic relationship (and it was one of my arguments against PEP695 - a battle I, unfortunately, lost). Consider django-stubs
(permalink to the declaration):
# __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]): ...
These fields do not have to resolve to the same type variables, obviously. However, _ST
and _GT
have a semantic meaning, which is same for all of them. This does not influence type checking, but helps those who read this code. If _GT
and _ST
were defined for each class separately, then, in addition to unreadable and unpleasantly looking syntax, we'd have to either repeat comments near every class or document that once in module docstring and add one extra lookup step to demystify their meaning. Using longer forms like _SetterType
and _GetterType
would remove need for explanation, but also make the already long-ish signatures longer. Additionally, explanation of how Field
typing works is still necessary (there are other caveats). Now it's explained in a docstring that comes right after typevar declaration, but with PEP695 style it'd be written far away.