pythonmypycrtp

How to implement type-safe CRTP in Python?


I'm familiar with curiously recurring template pattern (CRTP) implementation in C++ and C# programming languages. But, how we can achieve the same idea in the Python and type-safe (using Mypy)?

This code may not be functioning properly, but symbolically, it provides an idea:

class GenericParent(Generic[T]):
    pass

class Derived(GenericParent[Derived]):
    pass

I've tried specializing the GenericParent type variables by the Derived class but the Mypy gives error.


Solution

  • As a static typing pattern, CRTP should be supported out of the box across major Python type checking implementations. mypy itself should not give you errors when you declare class Derived(GenericParent[Derived]): ... (see the following mypy playground demo) - you might be seeing errors from another linter or the IDE you're using, because this code will fail at runtime.

    You have several options to make this work. Options 1 and 2 assumes that you don't need runtime introspection; option 3 will handle runtime introspection in a limited fashion.

    1. Write a .pyi stub file, which has no runtime implementation and thus won't cause any runtime errors. The CRTP pattern is used in Python's own typeshed to statically model the builtin str type.
    2. Put the typing information under if typing.TYPE_CHECKING along with from __future__ import annotations, which (IMO) is the cleanest way to handle forward references and circular imports in general.
    3. Use string literals to declare types, either directly or using explicit type aliases:
      import typing as t
      
      T = t.TypeVar("T")
      
      class GenericParent(Generic[T]):
          pass
      
      class Derived(GenericParent["Derived"]):
          pass
      
      Some helpers from typing can help perform introspecting this. On Python 3.10:
      >>> import typing as t
      >>> t._eval_type(Derived.__orig_bases__[0], globals(), {})
      __main__.GenericParent[__main__.Derived]