Type annotations in Python 3.6 and using Mypy as a static type checker

The main goal of type annotations is to open up Python code to easier static analysis and refactoring, potential runtime type checking, and (perhaps, in some contexts) code generation utilizing type information. These 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.

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 used. This function will now be treated as having no 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 types exported from typing (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 was 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 interfere when running the program. A function without a type annotation is considered dynamically typed.

Let's run mypy for checking a class method I wrote:

def parse_file_properties(cls, properties: str) -> OrderedDict:
    properties = properties.split('\n')
    list_properties = []
    for property in properties:
        key_value = property.split('=')
            list_properties.append((key_value[0].strip(), key_value[1].strip()))
        except IndexError: # The case where a property contains comment or a blank line
    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, so my 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:

Static type checking