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
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
Same with floats,
multiply(2.5, 2) and you get
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
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
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
times is of type
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.