Python is a dynamically typed language and allows us to operate fairly freely on variables of different types. However, when writing code, we somehow assume which types of variables will be used (this may be caused by a limitation of the algorithm or business logic). And for the program to work correctly, it is important for us to find errors associated with transferring data of the wrong type as early as possible.
Keeping the idea of dynamic duck typing in modern versions of Python (3.6+) supports annotations of variable types, class fields, arguments, and return values of functions:
- PEP 3107 — Function Annotations
- PEP 484 — Type Hints
- PEP 526 — Syntax for Variable Annotations
Type annotations are read by the Python interpreter and are not processed in any way but are available for use from third-party code and are primarily designed for static analyzers.
In this article, I want to explain the basics and give examples of using type annotations and eventually show why it made my life as a Python developer much easier 🙂.
First, let’s understand what type annotations are
▶ Type annotations — The basics
The types themselves are used to indicate the basic types of variables:
Unlike older versions of Python, type annotations are not written in comments or docstrings but directly in the code. On the one hand, this breaks backward compatibility. On the other, it clearly means that this is part of the code and can be processed accordingly.
In the simplest case, the annotation contains the directly expected type. More complex cases will be discussed below. If a base class is specified as annotation, it can pass instances of its descendants as values. However, you can use only those capabilities that are implemented in the base class.
Variable annotations are written with a colon after the identifier. This can be followed by value initialization. For instance:
price: int = 5
Function parameters are annotated in the same way as variables, and the return value is specified after the arrow
-> and before the trailing colon. Let me give an example of using type annotations in a python function:
For class fields, annotations must be specified explicitly when the class is defined. However, analyzers can automatically infer them based on the
__init__method, but in this case, they will not be available at runtime.
▶ Type annotations — Built-in types
Although you can use standard types as annotations, there is a lot of useful stuff hidden in the module
typing. Let’s take a look at its’ sub-modules.
If you mark a variable with a type
intand try to assign to it
None, there will be an error:
Incompatible types in assignment (expression has type "None", variable has type "int")
Exactly for such cases, the typing module provides an annotation
Optionalindicating a specific type. Please note that the type of an optional variable is indicated in square brackets.
Sometimes you don’t want to restrict the possible types of a variable. For example, if it really doesn’t matter, or if you plan on doing different types of handling yourself. In this case, annotation can be used
Any. It will not swear at the following code:
The question may arise, why not use
object? However, in this case, it is assumed that although any object can be passed, it can only be treated as an instance
For cases when it is necessary to allow the use of not all types, but only some, you can use the annotation
typing.Unionindicating the list of types in square brackets.
By the way, the annotation
Optional[T]is equivalent to
Union[T, None], although such a notation is not recommended.
The mechanism of type annotations supports the mechanism of generics (PEP484 — Generics, for more details in the second part of the article), which allows specifying the types of elements stored in them for containers.
PEP 484 -- Type Hints
PEP 3107 introduced syntax or function annotations, but the semantics were deliberately left undefined. There has now…
To indicate that a variable contains a list, you can use the list type as an annotation. However, if you want to specify which elements the list contains, such an annotation will no longer work. For this, there is
typing.List. Similar to the way we specified the type of an optional variable, we specify the type of the list items in square brackets.
The list is assumed to contain an indefinite number of similar items. But at the same time, there are no restrictions on annotation elements: You can use the
Listand others. If no element type is specified, it is assumed to be
In addition to the list, there are similar annotations for sets:
Tuples, unlike lists, are often used for different types of elements. The syntax is similar with one difference: the type of each element of the tuple is indicated in square brackets separately.
If you plan to use a tuple similarly to a list: store an unknown number of elements of the same type, you can use the ellipsis (
Tuplewithout specifying element types works the same way as
Used for dictionaries
typing.Dict. Key type and value type are annotated separately:
8️⃣ Function execution results
Any type annotations can be used to indicate the type of function result. But there are a few special cases.
If the function returns nothing (like how
None. We also use for annotation
The correct options for completing such a function are: explicit return
None, return without specifying a value, and termination without a call
If the function never returns control (for example, how
sys.exit), use the annotation
If this is a generator function, that is, its body contains an operator
yield, you can use the annotation for the returned one
Generator[YT, ST, RT]:
Instead of a conclusion
For many situations, the typing module has suitable types, but I will not cover everything, since the behavior is similar to those described. For example, there
Iteratoris a generic version for
typing.SupportsIntto indicate that an object supports a method
Callablefor functions and objects that support a method
The standard also defines the format of annotations in the form of comments and stub-files, which contain information only for static analyzers.
If you found this article helpful, click the💚 or 👏 button below or share the article on Facebook so your friends can benefit from it too.