Have you ever written Python code and had to dig deep into callstacks to understand what the return type of a function is?

I know I did and unfortunately, it makes me slower overall because I always have to dig through the callstack to understand what I’m dealing with. This also makes the code sensibly more complex given the number of additional checks that need to be implemented to assert a returned value is of an expected type. This is without taking into account unit tests that you need to write to test your assertion.

There is a way to simplify your life! Mypy is there to help us.

What is mypy

Mypy is a static type checker for Python. In other words, it tries to bring back to Python what other statically typed languages have: confidence in your function calls. It uses the Python 3 annotation syntax to help you find inconsistencies in your code.

How are type hints helping me

Let’s take the following example of a function that accepts a string and an integer and returns a string. This function concatenates the string a number of times equal to the integer.

def multiply(value, times):
    return value * times

Execute this function with multiply("a", 3) and you get aaa.

But given the function above, how would you know the behavior is meant for multiplying a string? You could use the same function to multiply an integer with an integer. multiply(2, 3) and you get 6.

Same with floats, multiply(2.5, 2) and you get 5.0.

But try multiply("a", 2.5) and you get a TypeError.

So how, do you indicate that this function is meant to receive a string for value and an integer for times?

You could raise errors if the type is not the expected one:

def multiply(value, times):
    if not isinstance(value, str):
        raise TypeError("value is not of type str")
    if not isinstance(times, int):
        raise TypeError("times is not of type int")
    return value * times

While it works, it makes the code itself more complex and you would need to write unit tests to cover these assertions. It provides information to the developer using your function that value is of type str and times of type int. But the developer has to read the source of your function to know this information.

If you use type hints instead, you can avoid these assertions:

def multiply(value: str, times: int) -> str:
    return value * times

When you call the multiply function, your IDE should show you the signature of the function and that gives you the information that value is of type str and times is of type int.

While Python allows you to type hint your functions, the runtime does not enforce them.

I can still call the function with two integers and the result will be the integer multiplication.

How to enforce type hints

This is where mypy enters. Mypy is a tool that runs at development time and finds all the type hint inconsistencies in your code.

Let’s assume that we type hint the function def multiply(value: str, times: int) -> str. If we call it with multiply(2, 3), running mypy on the code will generate the following error:

error: Argument 1 to “multiply” has incompatible type “int”; expected “str”

If I never inspect the code with mypy, the call could still end up in my production environment.

Mypy is as strong as you are willing to let it be. I will cover more mypy features in other blog posts as well as how to automate mypy checks on your code.