Python typing 2021

Define a bunch of types aliases and you should be fine

Typing haters will obviously blame typing… for having a variable which stores a list of lists of iterables of lists of segments

Define a bunch of types aliases and you should be fine.

../../../_images/aliases.png

Python Type Hints - How to Fix Circular Imports

So, how can we fix this ?

The answer is to use the special typing.TYPE_CHECKING constant. This is hardcoded to False, but set to True by type checkers like Mypy. We can use it to make the import in controllers.py conditional:

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from models import Book


class BookController:
    def __init__(self, book: "Book") -> None:
        self.book = book

...

Now the code can run as before, and mypy can still check our types.

Note that we had to change the Book type hint into a string.

This isn’t necessary if we add from __future__ import annotations at the top of the file, which makes all annotations strings by default (see PEP 563). (__future__.annotations will become the default in Python 3.11).

For example:

from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from models import Book


class BookController:
    def __init__(self, book: Book) -> None:
        self.book = book

    ...

Nice.

Python Type Hints - How to Enable Postponed Evaluation With __future__.annotations

Introduction

Python 3.7 added a new “postponed evaluation” mode for type hints.

This is opt-in for now, and may become the default in Python 3.11.

In this post we’ll cover how it changes the behaviour of type hints, how to activate it, and the outstanding problems.

What is postponed evaluation ?

When Python introduced type hints, the team decided type hint expressions should execute at runtime. This allowed flexibility and experimentation with the syntax.

Unfortunately it also meant that expressions have to follow Python’s rules about variable scope. This made forward references, to classes that aren’t yet defined, problematic.

For example, take this class that can copy itself:

from copy import deepcopy


class Widget:
    ...

    def copy(self) -> Widget:
        return deepcopy(self)

The -> Widget return type hint is a forward reference, as the Widget class is not defined until the end of its class statement.

With the old behaviour, Python tries to evaluate this reference, causing a NameError:

PEP 563 defined the new postponed evaluation behaviour. This moves all type hints to follow this pattern, without requiring the string syntax.

We can opt into postponed evaluation in a file by adding:

from __future__ import annotations at the top.

In our example:

from __future__ import annotations

from copy import deepcopy


class Widget:
    ...

    def copy(self) -> Widget:
        return deepcopy(self)

Python no longer interprets the type hints at runtime, so it doesn’t hit the NameError. And type checkers can still work with the type hints as they evaluate them after reading the whole file.

Python Type Hints - How to Narrow Types with isinstance(), assert, and Literal

Introduction

Type narrowing is the ability for a type checker to infer that inside some branch, a variable has a more specific (narrower) type than its definition. This allows us to perform type-specific operations without any explicit casting.

Type checkers, such as Mypy, support type narrowing based on certain expressions, including isinstance(), assert, and comparisons of Literal types.

In this post we’ll explore those type narrowing expressions, using reveal_type() to view the inferred types.

We’ll use Mypy, but other type checkers should support narrowing with a similar set of expressions.

Note that this list of expressions is non-exhaustive.

Type checkers may support more Python syntax, now or in the future, for example Python 3.10’s structural pattern matching.

Custom type narrowing logic is also possible, thanks to PEP 647 which introduced typing.TypeGuard.

This is available on Python 3.10+ or in typing-extensions.

Guido van Rossum added support to Mypy but this hasn’t yet been released. TypeGuard seems like a good subject for a future post!