Python type hints: specify a class rather than an instance thereof

In a type hint, if we specify a type (class), then we mark the variable as containing an instance of that type. To specify that a variable instead contains a type, we need to use type[Cls] (or the old syntax typing.Type).
For example, take this code:
class Animal:
def __init__(self, *, name: str):
self.name = name
class Cat(Animal): ...
class Dog(Animal): ...
def make_animal(animal_class, name):
return animal_class(name=name)
animal = make_animal(Dog, "Kevin")
We need to add type hints for make_animal(). How can we specify that its animal_class argument should contain a subclass of Animal?
We could try using Animal:
def make_animal(animal_class: Animal, name: str) -> Animal:
return animal_class(name=name)
The type of animal_class looks the same as the return type though, even though we know the first is a class and the second an instance. And indeed, Mypy flags some errors:
$ mypy example.py
example.py:15: error: "Animal" not callable
example.py:18: error: Argument 1 to "make_animal" has incompatible type "Type[Dog]"; expected "Animal"
Found 2 errors in 1 file (checked 1 source file)
What we should use instead is type[Animal]:
def make_animal(animal_class: type[Animal], name: str) -> Animal:
return animal_class(name=name)
This means “animal_class takes the class Animal or any subclass”. Running Mypy on this produces no errors.
The type[] syntax was only added in Python 3.9, thanks to PEP 585. Mypy 0.800+ supports this new syntax on older Python versions if you use from __future__ import annotations.
The previous syntax is typing.Type[Class]:
from typing import Type
...
def make_animal(animal_class: Type[Animal], name: str) -> Animal:
return animal_class(name=name)
If your code uses typing.Type and you add __future__.annotations or upgrade to Python 3.9+, you can update your old syntax with pyupgrade.
😸😸😸 Check out my new book on using GitHub effectively, Boost Your GitHub DX! 😸😸😸
One summary email a week, no spam, I pinky promise.
Related posts: