Clarifying the rules for subclassing generic classes

Currently, the spec has this to say about creating a generic class by subclassing another generic class in the pre-PEP 695 world:

The Generic[T] base class is redundant in simple cases where you subclass some other generic class and specify type variables for its parameters:

from typing import TypeVar
from collections.abc import Iterator
T = TypeVar('T')
class MyIter(Iterator[T]): ...

That class definition is equivalent to:

class MyIter(Iterator[T], Generic[T]): ...

It would be nice to clarify what those “simple cases” are, given that typeshed uses this, and it’s currently unclear what cases a type checker needs to support to use typeshed.

As far as I can tell, both mypy and pyright take type variables in order of first appearance, with pyright additionally emitting an error when this doesn’t produce a consistent ordering. Examples:

from typing import Generic, TypeVar
T1 = TypeVar('T1')
T2 = TypeVar('T2')
T3 = TypeVar('T3')
class C1(Generic[T1, T2]): ...
class C2(Generic[T1, T2]): ...

class C3(C2[T1, T3], C1[T2, T3]): ...
c3 = C3[int, str, bool]() # T1 = int, T3 = str, T2 = bool

(mypy, pyright)

from typing import Generic, TypeVar
T1 = TypeVar('T1')
T2 = TypeVar('T2')
class C1(Generic[T1, T2]): ...
class C2(C1[T1, T2]): ...

class C3(C2[T1, T2], C1[T2, T1]): # this is an error in pyright but not mypy
    ...
c3 = C3[int, str]() # T1 = int, T2 = str

(mypy, pyright)

I think the simplest thing to do would be to update the spec to say that type checkers should take type variables in order of first appearance, and may warn when the order is inconsistent. Thoughts?

3 Likes

I think the simplest thing to do would be to update the spec to say that type checkers should take type variables in order of first appearance, and may warn when the order is inconsistent.

That sounds correct to me.

Here’s a related topic that is currently unspecified. Mypy treats Protocol like Generic in that they both override the “first appearance” ordering.

from typing import Protocol, TypeVar

T1 = TypeVar("T1", covariant=True)
T2 = TypeVar("T2", covariant=True)

class Proto1(Protocol[T1, T2]): ...

class Proto2(Proto1[T1, T2], Protocol[T2, T1]): ...

Proto2[int, str]  # T1 = str, T2 = int

And this also means it’s an error to specify the ordering using both a Generic and a Protocol.

class Proto1(Protocol[T1, T2], Generic[T2, T1]): ... # Type checker error

I don’t see any place in the typeshed stubs that rely on this behavior, but I have encountered it in other stubs and libraries, so it would be good to standardize.

4 Likes

Agreed, that seems like it would be good to standardize as well. I’ll take a stab at an update that addresses both of these.

2 Likes

Closing the loop: spec has been updated in Clarify some behavior around user-defined generic classes by rchen152 · Pull Request #1879 · python/typing · GitHub.

2 Likes