Python Type Hints - Use Cases for the types Module

2021-09-08 Some kind of tool for working with types.

Writing type hints gives us some familiarity with the typing module. But Python also includes the similarly-named types module, which can also come in handy. Let’s look at the history of these two modules, some use cases of types, and one way in which it’s not so useful.

History

Chronologically, types came first, by a long way. types had its first commit in 1994 from Guido van Rossum. The module allows us to access special types that we cannot otherwise reference without a little “gymnastics”. For example, None is a special singleton made by the Python interpreter’s machinery, so there’s not normally a name bound to its type. But we can find a reference with the type() built-in like so:

NoneType = type(None)

…and indeed this is what types module does, for None and many other types.

The typing module was added following the big type hints proposal, PEP 484. Its first commit was in 2015, again by Guido van Rossum. It stores many different tools for writing type hints, and has evolved a lot in recent versions alongside the story for type hints.

ModuleType

types.ModuleType is the type of a Python module. This can come in useful when dealing with dynamic imports, such as the references in Django’s settings files. For example, we could write a function to import a list of modules by name like so:

from __future__ import annotations

from importlib import import_module
from types import ModuleType


def load_modules(names: list[str]) -> list[ModuleType]:
    return [import_module(name) for name in names]


modules = load_modules(["sys", "os.path", "pathlib"])

TracebackType

types.TracebackType represents a traceback. In most programs these are only constructed by Python’s exception-handling machinery. (Indeed, the ability to construct tracebacks directly was only added in Python 3.7.)

In code handling tracebacks, we may need to reference TracebackType in our type hints. For example, in the previous post How to Type a Context Manager, we needed to accept a traceback in __exit__():

from __future__ import annotations

from types import TracebackType


class MyContextManager:
    def __enter__(self) -> None:
        pass

    def __exit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> None:
        pass

Not so useful: FunctionType, GeneratorType, and CoroutineType

Some types in types having typing equivalents that take type parameters to be more specific. These equivalents started in the typing module, but in version 3.9 they moved to combine with the versions in collections.abc. The types types and their equivalents are:

With type hints these types versions are not so useful, as Mypy doesn’t recognize them.

For example, take this program:

from __future__ import annotations

from types import FunctionType


def f() -> int:
    return 12


x: FunctionType = f

We try to store a function f in variable x, which we declare as containing a function. This works perfectly fine at runtime, but Mypy thinks it is incorrect:

$ mypy example.py
example.py:11: error: Incompatible types in assignment (expression has type "Callable[[], int]", variable has type "FunctionType")
Found 1 error in 1 file (checked 1 source file)

This behaviour is because Mypy treats functions (of all kinds) specially internally.

Therefore when working with these types, we need to stick to the versions from collections.abc (or form typing on Python < 3.9).

GenericAlias

Python 3.9 gave us the ability to parametrize built-in types directly, such using list[int] instead of typing.List[int]. This is great for writing type hints. But what is the type of list[int]?

It turns out to be accessible as types.GenericAlias. For example, we can also construct list[int] like so:

from types import GenericAlias

GenericAlias(list, (int,))

GenericAlias can be useful when introspecting type hints, like the attrs library does.

Fin

May your types be ever more accurate,

—Adam


🎉 My book Speed Up Your Django Tests is now up to date for Django 3.2. 🎉
Buy now on Gumroad


Subscribe via RSS, Twitter, or email:

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

Related posts:

Tags: mypy, python