Code sample in pyright playground
from typing_extensions import TypeVar
from typing import Generic, Container
X = TypeVar("X", str, int, str | int, covariant=True, default=str | int)
Y = TypeVar("Y")
class A(Container[X]):
def __init__(self, *items: X, other: X):
self._items: Container[X] = items
self._other: X = other
def __contains__(self, x: object, /) -> bool:
return self._items.__contains__(x)
class B(Container[X], Generic[Y, X]):
def __init__(self, *items: X, other: Y):
self._items: Container[X] = items
self._other: Y = other
def __contains__(self, x: object, /) -> bool:
return self._items.__contains__(x)
def takes_container(x: Container[int]):
...
def example():
a: A[int | str] = A(1, other="this")
a2: Container[int] = a
b: B[str, int] = B(1, other="this")
b2: Container[int] = b
takes_container(a)
takes_container(b)
This matches both my intuition and what type checkers do, and what is specified about the behavior of type variables.
to be clear, I agree that class B(A[X])
means all B[X]
are A[X]
, and same with class B(A[X], Generic[X])
, for the same reason that class B(A)
means that all B
are A