Python Type Hints - How to Specify a Class Rather Than an Instance Thereof

2021-05-16 Cats & Dogs 4: The Battle of the Types

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.

Fin

Dogs clearly beat cats,

—Adam


Want better tests? Check out my book Speed Up Your Django Tests which teaches you to write faster, more accurate tests.


Subscribe via RSS, Twitter, or email:

One summary email a week, no spam, I pinky promise.

Related posts:

Tags: mypy, python