pythongenericspython-typingmypytype-alias

Type alias for type of typevar and mypy


I have a type var that represents a child of specific interface

BuildableChild = TypeVar("BuildableChild", bound=Buildable)

The content of Buildable does not really matters, let's assume it is just

class Buildable: ...

Now I want to have a function, that accepts a class of this type (i.e. a child class) and an optional filter function, which also accepts a buildable class:

def do_work_on_buildable_class(
    buildable: type[BuildableChild],
    filter_fn: Callable[[type[BuildableChild]], bool] | None = None,
) -> list[type[BuildableChild]]:
    pass

Since type[BuildableChild] is reused multiple times, I thought I could make an alias for it to reduce the number of brackets that you need to read:

from collections.abc import Callable
from typing import TypeAlias, TypeVar

class Buildable: ...

BuildableChild = TypeVar("BuildableChild", bound=Buildable)
BuildableChildType: TypeAlias = type[BuildableChild]

def do_work_on_buildable_class(
    buildable: BuildableChildType,
    filter_fn: Callable[[BuildableChildType], bool] | None = None,
) -> list[BuildableChildType]:
pass

It seems correct to me, but mypy is raising the Missing type parameters for generic type "BuildableChildType" error for these lines:

    buildable: BuildableChildType,
    filter_fn: Callable[[BuildableChildType], bool] | None = None,
) -> list[BuildableChildType]:

So it assumes that type[BuildableChild] is generic, and I think it is technically correct, but how else I would write a type alias for a type of TypeVar?

I don't think it is a solution to use BuildableChildType[Any] because it's kind of ruins the whole idea of using the alias.

I did also try to rewrite

BuildableChildType: TypeAlias = type[BuildableChild]

to

type BuildableChildType = type[BuildableChild]

but then mypy is raising All type parameters should be declared ("BuildableChild" not declared) error for this line


Solution

  • There is a difference between the following:

    1. BuildableChild = TypeVar("BuildableChild", bound=Buildable)
      BuildableChildType: TypeAlias = type[BuildableChild]
      

      This is a concrete generic type (like list). If you go to the type definition of list, you can see that it has a free type variable _T:

      ...
      
      _T = TypeVar("_T")
      
      ...
      
      class list(MutableSequence[_T]):
          ...
      

      If you need to use list in a type expression, you'll have to fully parameterise it, otherwise mypy will emit errors (Missing type parameters for generic type ...).

    2. BuildableChildType = TypeVar("BuildableChildType", bound=type[Buildable])
      

      I believe that this is what you're actually looking for - a free type variable which statically ensures that the argument to parameter buildable, input to filter_fn, and the return type of do_work_on_buildable_class are related. See mypy Playground.

    (1) and (2) mean different things, though, so you'll need to solve a concrete programming or static typing issue to choose between them, rather than your stated reason in the question:

    I thought I could make an alias for it to reduce the number of brackets that you need to read