The main goal of type annotations is to open up Python code for static analysis. It makes it easier to debug and maintain code because each type is explicitly stated. It also makes the code review process simpler as the parameters and return types can be inferred from the function header. These changes were introduced in PEP 484.
In this regards, static type checking is the most important. It allows support for off-line third-party type checkers, such as Mypy, which will be introduced in a later section.
Purpose of annotations
The typing
module in Python 3.6 contains many definitions that are useful in statically typed code. For instance, the Any
type is used by default for every argument and return type of a function. This is all in regards to a checked function. If a function is to be ignored by a static type checker, the decorator @no_type_check
should be provided before the function header, and the function will not be treated as having type annotations.
An example following the type annotations is shown below:
def scale(scalar: int, number: int) -> int:
return scalar * number
This states that the expected type of the scalar
and number
argument is int
and int
, respectively. Analogically, the expected return type is also int
.
The acceptable type hints that may be used are: None
, Any
, Union
, Tuple
, Callable
, all of which are exported from the typing
module (e.g. Sequence
and Dict
), type variables, and type aliases.
Using type aliases
Type aliases can be defined by using simple variable assignments:
Temperature = float
def forecast(temperature: Temperature, day: int) -> str: ...
Creating your own types
In Python 3.5 a new function is introduced called NewType
. This allows the programmer to create distinct types:
from typing import NewType
UserId = NewType('UserId', int)
some_id = UserId(356)
Static type checking using Mypy
When a script is run with a standard Python interpreter, the type annotations are treated primarily as comments. Using mypy
, common code bugs can be found and it checks the code for proper return types etc. As mypy
is a static analyzer, it does not cause any overhead when running the program.
Note: A function without a type annotation is considered dynamically typed.
Let’s run mypy
for checking a class method I wrote:
@classmethod
def parse_file_properties(cls, properties: str) -> OrderedDict:
properties = properties.split('\n')
list_properties = []
for property in properties:
key_value = property.split('=')
try:
list_properties.append((key_value[0].strip(), key_value[1].strip()))
except IndexError: # The case where a property contains a comment or a blank line
pass
return OrderedDict(list_properties)
This function takes in a =
separated string, parses it using split()
, and returns it as an ordered dictionary. When I run mypy
from a terminal, issuing the command python -m mypy hips/tiles
, I get no error or warning, which indicates that the provided type annotations were correct.
However, if I change the function header to:
def parse_file_properties(cls, properties: int) -> OrderedDict:
I get this error message:
hips/tiles/description.py:61: error: “int” has no attribute “split”
The illustration below shows the basic workflow of static type checking: