Setup mypy for fun and profit
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.