Dark theme

Writing type hints


Type hints are based on "annotations", a format for marking up variables and functions with extra information (PEP 3107). In Python 2, these were put in comments, but in Python 3 they moved into the actual code. Because type hints are a type of annotation, they are sometimes called "type annotations". Here's what our add function would look like with type hints (safer.py; download this):
def add(a: int, b: int) -> int:
    return a + b

var_a: int = 1
var_b: int = 2
print(add(var_a,var_b))

Even with this addition to the code, it would still run in Python 3.6 (Python 3.5 will object to non-function annotations). The use of type annotations for variables is outlined in PEP 526, and functions in PEP 484, with a detailed description of their use in PEP 483 and a brief introduction in the documentation for the typing library.

To run a 'static type test', as it is called (after static languages which tend to pre-define types rather than dynamically guessing them), we need a separate piece of software. The most popular is mypy. The easiest way to install this so it works with Anaconda is:
conda install -c anaconda-platform mypy
If you're not using Anaconda, use: pip install mypy
Once this is installed, go to the directory containing the safer.py and type:
mypy safer.py

mypy should pass the file without saying anything. This means it is fine.


Now try changing the variable values to strings by putting quotes around them:
var_a: int = "1"
var_b: int = "2"

You should see the following report:
safer.py:5: error: Incompatible types in assignment (expression has type "str", variable has type "int")
safer.py:6: error: Incompatible types in assignment (expression has type "str", variable has type "int")


Reset the variables back to ints, but now change the function call to:
print(add([var_a],[var_b]))
That is, passing in lists not int. Re-run mypy and you should see:
safer.py:7: error: Argument 1 to "add" has incompatible type "List[int]"; expected "int"
safer.py:7: error: Argument 2 to "add" has incompatible type "List[int]"; expected "int"

If we wanted to set the type to list, we'd use (note the import):
from typing import List

def add(a: List, b: int) -> int

Or, better:
from typing import List

def add(a: List[int], b: int) -> int

with the equivalent change to the variables.


Reset the function call to ints, and, finally, adjust the return type of the function thus:
def add(a: int, b: int) -> int:
    return (a + b,)


i.e. so it returns a tuple. Run mypy again and you should see:
safer.py:3: error: Incompatible return value type (got "Tuple[int]", expected "int")
Note that if we wanted to say the function returned nothing, we'd use:
def add(a: int, b: int) -> None:


As you can see, type hints allow us to check, prior to running the code, that the variable types match up, both in terms of assignments, function calls, and returns. This is super-useful in avoiding the bugs that arise from duck typing, which are especially are to find as variables can remain the wrong type all the way through the code until something really unpleasant happens. If you're going to build really solid code, type hints are really useful.

As well as the basics above, the type annotation system in Python has a great deal of depth, from the medium complicated: how to return something or nothing; to the more complicated: how to build "generics", that is, classes that can have a type imposed on them as the code runs.

If you're going to build code that has a hi-risk remit, it is definitely worth looking in detail at type hints.