Sam Hooke

mypy tips

Following are some tips from my experience of using mypy.

Making your module type checking compatible §

Follow the steps in the mypy documentation on “Making PEP 561 compatible packages”.

In short, this consists of:

  • Add an empty py.typed file into your module’s root (adjacent to the __init__.py file).
  • Add package_data={"": ["py.typed"]} to your setup.py to include the py.typed at build time.

It should now be possible for other modules to import this module, and avoid type checking errors upon import. Of course, you’ll still need to add type hinting to your module for this to actually be useful.

If you get errors, mypy has good documentation on how to resolve these.

Note that there are two other ways of enabling type checking, using stub files, or using stub only packages. My preference is for the first method of inline type annotations.

Cyclical imports §

Adding type checking can introduce cylical imports.

This can be avoided through first using if TYPE_CHECKING: to conditionally import the necessary modules. This will be True when type checking is performed, and False otherwise (e.g. at runtime).

Then, put the affected imports as strings, e.g. "MyClass". This allows the imports to not be present at runtime, but when type checking occurs, mypy will infer the type from the string.

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from my_module import MyClass

def foo(bar: "MyClass") -> None:
    pass

Optional imports §

Alternatively, type checking can require all optional dependencies to be present, which can be problematic!

For example, consider having module A, which at runtime depends upon having module B xor C present - it doesn’t require both. However, the type checker does require both. This contradiction can be resolved using the same method as for resolving cyclical imports:


from typing import Union, TYPE_CHECKING

if TYPE_CHECKING:
    from module_b import B
    from module_c import C

def foo(bar: Union["B", "C"]) -> None:
    pass

Signal handling §

For signal handlers:

from types import FrameNum
import signal

signal.signal(signal.SIGINT, self.exit_handler)
signal.signal(signal.SIGTERM, self.exit_handler)

def exit_handler(signum: int, frame: FrameType) -> None:
    pass

Common Uncommon Types §

The common types such as int and str and quite straight-forward. But some of the less-common common types are not quite so straight-forward.

Queue §

In Python 3.6 or below, the queue.Queue type requires specifying as a string, e.g.:

import queue

my_q: "queue.Queue[int]" = queue.Queue()

In Python 3.7 or above, you can put from __future__ import annotations in your imports and use Queues as normal, e.g.:

from __future__ import annotations
import queue

my_q: queue.Queue[int] = queue.Queue()