Properties2
TypePractice
Note createdFeb 17, 2025

Typing in Python is completely optional, since it is, by default, dynamically typed. Since PEP 484, using type annotations and a static type checker (the evolution of mypy) is possible in Python. This type annotations will have several benefits, like improving text editor support on variable hinting and completion and better static analysis using the new typing system.

Typing basics

Typing in Python is gradual, meaning that some parts of the code can include annotations and others don’t. However, code that does have annotations must conform to them.

If two types (T1 and T2) are considered within a gradual type system, T1 is consistent with T2 as long as value of type T1 can be assigned to a variable of type T2. This relationship is not symmetric, nor transitive. The exception to this rule is the type Any: it is consistent with any type and any type is consistent with Any.

The typing module includes several objects that can add magic types: List, Dict, Iterable and Tuple can receive arguments to define it more. For example, a list of 2D points can be defined as List[Tuple[float, float]].

It is important to understand that types are for the checker, while classes are for run-time. However, classes can be used as types (but some types are not classes).

The typing module

A Tuple has a set of arguments defining which elements should contain, for example Tuple[str, int, int]. To represent an immutable sequence, one can write Tuple[float, ...].

On the other hand, Callable can restrict both arguments and return types. For example, a function with two float arguments that return a string can be defined as Callable[[float, float], str]. In case the arguments don’t need to be defined, Callable[..., str].

It is also possible to recreate generics by using TypeVar and Generic, although it is usually only needed when defining new container types. For everyday use, it is usually enough to define T = TypeVar('T') and use it as the generic label, i.e List[T].

Union can be useful sometimes, to define a group of accepted types. However, it can lead to too-liberal type checking. Another option is to use extra arguments in a TypeVar to define valid constraints: if we declare a new type, AnyStr = TypeVar('AnyStr', str, bytes), it will check for any subclass of any of these types, while forcing them to be equal.