Why does `ClassVar` disallow type variables?

PEP-526 states

Note that a ClassVar parameter cannot include any type variables, regardless of the level of nesting: ClassVar[T] and ClassVar[List[Set[T]]] are both invalid if T is a type variable.

I’m curious why this restriction exists. It’s not stated in the PEP itself; perhaps it’s somewhere in the annals of the Typing github…

This question arose while I was trying to write and type a generic base class that has two members where one is an instance variable holding an instance of some type and the other is a class variable holding the type itself:

class _ApiClientBase[C: (httpx.Client, httpx.AsyncClient), Auth: httpx.Auth](ABC):

    base_url: str
    client: C
    _auth: Auth
    _auth_cls: ClassVar[Type]  # I wanted ClassVar[Type[Auth]]

    @property
    def auth(self) -> Auth:
        return self._auth

    @auth.setter
    @abstractmethod
    def auth(self, value: Any):
        if not isinstance(value, self._auth_cls):
            self._auth = self._auth_cls(value)
        else:
            self._auth = value

The restriction on ClassVar means I must give up either marking the attribute as a class variable, or I must give up marking its relationship to the type of _auth.

This is a rough draft design that certainly will not survive revision. Suggestions are welcome, but my question here is about ClassVar.

A ClassVar occurs once per class, not once for each generic parametrization.
To give a (different) example, suppose we implement a generic linked list.

@dataclass
class Node[T]:
    value: T
    next: Self | None = None


class LList[T]:
    head: ClassVar[Node[T] | None] = None

    def __str__(self) -> str:
        cls = type(self)
        values = list[T]()
        p = cls.head
        while p:
            assert isinstance(p, Node)  # help typechecker understand
            values.append(p.value)
            p = p.next
        return f'{cls.__qualname__}({', '.join(map(repr, values))})'


ints = LList[int]()
strs = LList[str]()

LList[int].head = Node[int](1, Node[int](2))
print(ints)  # 1, 2
LList[str].head = Node[str]('hello', Node[str]('world'))
print(strs)  # 'hello', 'world'
print(ints)  # surprise! 'hello', 'world'

The linked list of ints shares its .head with the linked list of strs. There’s only one.
That seems to me there’s no point in using a TypeVar in a ClassVar.

1 Like

Okay, I think I get the rationale.

My goal was for implementations of the ABC to override the ClassVar with a more specific type, and declaring it in the ABC was essentially just documentation since the ABC itself would never have a value for that attribute.

I was also getting the run-around because authlib is using httpx2 while my codebase was using httpx, where the names are all the same but the types are technically different. Updating that let me directly use the base class I was trying to parameterize over in the ClassVar declaration.