Implicit type parameter values for bare names of generic classes

Hi folks, I’d like clarification on how a type annotation consisting of the bare name of a generic class whose type parameters have default values should be interpreted. For example:

T = TypeVar("T", default=int)
class Foo(Generic[T]):
    ...
def func() -> Foo:
    ...

How is Foo in the return type of func interpreted? I can’t find an explicit answer to this question in the typing spec, although there are various places which suggest the answer should be Foo[int]. That interpretation seems reasonable, although it’s a little surprising to me because of the following consequences:

  1. Foo[str] would not be a subtype of Foo (although it would be of Foo[Any])
  2. Adding a default value to a type variable could cause some previously-valid code to no longer type check, e.g. an implementation of func returning a Foo[str]. (I wouldn’t have expected this since there’s no discussion in PEP 696 of potential backwards-compatibility issues like having to change annotations of Generator to Generator[Any, Any, Any]. This post was prompted by typeshed recently adding default values to Generator, which if I understand correctly changed the meaning of a bare Generator annotation.)

I’d appreciate any thoughts on this, or pointers to where this case is specified.

This behavior is described in PEP 696, which introduced type parameter defaults. It’s also incorporated into the typing spec in this section.

Your interpretation is correct. If a generic class or type alias is parameterized by a type parameter with a default, an omitted type argument takes on the value of the default. In your example, the type annotation Foo would be interpreted as Foo[int].

Foo[str] would not be a subtype of Foo

Yes, the type expression Foo (with no subscript) is interpreted as type Foo[int] in your example. And yes, Foo[int] is not assignable to Foo[str] or vice versa.

Adding a default value to a type variable could cause some previously-valid code to no longer type check

Yes, that’s correct. Library and stub maintainers will need to take care if they choose to add defaults to a generic class whose type parameters previously did not have defaults. Such a change can have backward compatibility implications, and those should be weighed against any benefits provided by the change.

3 Likes