In Python 3.12, the new Generic / TypeVar syntax allows us to do this:
class Foo[T, U]: type: T info: U class Bar[T](Foo[str, T]): test: T
Which, for older versions is functionally equivalent to this:
from typing import Generic, TypeVar FooT = TypeVar('FooT') FooU = TypeVar('FooU') class Foo(Generic[FooT, FooU]): type: FooT info: FooU BarT = TypeVar('BarT') class Bar(Foo[str, BarT], Generic[BarT]): test: BarT
Key point being that each
TypeVar is unique to the class.
However, in older versions it is much more common to reuse the same
TypeVars for everything, as even some code in PEP 484 showcase doing.
from typing import Generic, TypeVar, get_type_hints T = TypeVar('T') U = TypeVar('U') class Foo(Generic[T, U]): type: T info: U class Bar(Foo[str, T], Generic[T]): test: T
This however, poses a risk at runtime, which has incidentally been solved by the new syntax. When resolving the types of
test: T will resolve to the same TypeVar as
type: T, when we expect
type: T of Foo (later becoming
info: T of Bar.
I encountered this while investigating a bug with Pydantic not finding a
TypeVar with a default (testing out PEP 696) that had its value overridden by an inherited generic alias. The reason was because it was not resolving
TypeVar values all the way up, only on the first level (e.g.
Bar[bool] meant all
T = bool, but no type for
U). Initially, I had written a utility function (gist) to find the corresponding overriding values given a set of
TypeVars that are being used as a type hint for attributes of the generic class. It works given most conditions, and should work reliably on Python 3.12 as the
TypeVars are unique.
But this solution does not reliably work on versions older than that, and I was wondering what could be done to solve this.
Typecheckers (namely pyright and mypy that I’ve tested) on the other hand are able to solve this across all versions.
Let me know if I somehow missed out a key bit that would fix this.