Mypy and Continuous Integration sequence part 1: Mypy and Type Hints

Quinn Dougherty
5 min readJul 14, 2019

--

Introduction

As of Python 3.5, type hints are supported along with a static type checker called mypy.

https://github.com/python/mypy

Today, we will show how a simple program can be improved by type hints.

Then, I will introduce the two most basic concepts in type-centric programming, per Python’s notation: Callable and Optional .

Finally, we will open up travis and github to walk through how to configure continuous integration. As a bonus round, I’ll also show using docker in continuous integration.

If at any point I lose you in the discussion of None, please review truthyness and falseyness.

a simple program

In World of Warcraft, human merchants of Elwynn Forest declare “I buy and trade” as a generic greeting. The zombies of Tirisfal Glades, on the other hand, inform the player “I haven’t got all day” as their greeting.

Let’s suppose that a greeting can either be personalized at a player’s name, or generic, depending on if you left click to “bump into” them or right click to open a trade window.

We will presume a player class, that has at least the attribute name. The function can take a falsey value like None if no player is available. Passing None to these functions will simulate the case of bumping into the merchant, and passing a player into these functions personalizes the greeting.

We can “buy bread” if we presume some amount of gold as a player attribute, and we also want to pass in one of these functions to show what location we’re in.

We have a simple program that initializes two players, and simulates merchant interaction once in Elwynn Forest and again in Tirisfal Glades.

type annotation

The program is simple- but as we add more merchant interactions, will it be resilient to error? How about readability, is the data flow as clear as possible?

Since Python 3.5, we’ve been encouraged to write type hints by PEP 484. Python is still dynamically typed, but hints allow your programs to be verified by a static type checker called Mypy. We will use Mypy later on today, but first we have to provide the type hints.

I can write a function foo that prints a number as follows. Python type hinting puts the type of an argument after a : , and the type of a return after a ->.

With that in mind, let’s type hint our first two functions

As you can see, the name of our Player constructor plays the roll of the argument type. In Python, classes behave as types.

Function types: Callable

We want to type-hint our next functions, buy_bread which purchases bread from the merchant and bump which triggers a greeting but doesn’t do anything else. What is the type of the argument location? Scroll up to read the code, and think about it for a minute before reading on.

In the program, we supply elwynn and tirisfal to the argument location. Since elwynn and tirisfal are functions, their type is a function type. A function type is some A -> B where A and B are types. In our case, elwynn : Player -> str, as is tirisfal.

Python’s notation for function types is Callable. Taking the example foo from above, you might write on your scrap paper that foo is of type int -> str, but in Python you’d write that foo is of type Callable[[int], str].

Now we can type-hint our merchant interaction functions

type checking: mypy

Now that we have type hints in place, we can check or verify our code.

type checking in python is an optional process before the compiler that requires an auxiliary tool called Mypy.

Straight from the docs,

If you sprinkle your code with type annotations, mypy can type check your code and find common bugs. As mypy is a static analyzer, or a lint-like tool, the type annotations are just hints for mypy and don’t interfere when running your program. You run your program with a standard Python interpreter, and the annotations are treated effectively as comments

If you’re running Python 3.5 or later, you can install mypy with pip then run in your terminal

mypy program.py

or hand it a directory and it will process every .py file in it. When it runs, scans over all your type hints and shows points you toward where exactly your program isn’t guaranteed to succeed.

This is a class of errors that dynamically typed languages normally can’t warn you ahead of time about, and while mypy doesn’t catch everything, it’s a clear improvement.

Let’s try it!

mypy merchant.py

Thinks for a moment, and returns

merchant.py:32: error: Argument 1 has incompatible type "None"; expected "Player"

Let’s take a look at line 32.

Notice that we call location(None), where location is a function with arg type Player. You may have wished to point out to me above, None is not of type Player. We achieved falseyness in our location functions by assuming that sometimes, None would be passed in.

“Either” and “the type with one value”

Python, like many programming languages, reserves a type for the property of their being exactly one value satisfying that type.

Go ahead and type type(None) into a Python console. It tells you that None is of type NoneType.

None : NoneType

NoneType is the type with exactly one value, and None represents the absence of value, so it’s falsey. Still with me? Good.

Since I didn’t define an “empty Player” to represent falseyness, I know I need to use None. However, I also need to square that up with my type hints.

Since the argument of elwynn is either None or a value of type Player, the argument type of elwynn is either NoneType or Player. In PEP 484, the word for either is Union.

A quick caveat: NoneType isn’t actually in the namespace, and None is a valid term on both the type-level and the value-level.

So the argument type of elwynn looks exactly like Union[None, Player].

A value or None: Optional

Union[None, A] for any type A is such a fundamental Union/Or/Either type that programmers gave it its own keyword. Python decided to call it Optional. Optional[A] means we might have a value of type A, but we might not.

Further reading on Union and Optional at PEP 484.

With that, our functions can have complete type hints, like so

And now, if you run mypy merchant.py, it prints zero warnings!

TLDR; the need for falseyness, fulfilled by None, can be well-typed with Optional.

Type aliases

Callable[[Optional[Player]], str] isn’t very nice on the eyes, so let’s assign it to a name and only throw that name around.

Our completed program, completely typesafe, is as follows

Verify it yourself with python 3.7 and mypy!

Conclusion

You can type-hint your Python programs to make them more reliable and more readable. You can type functions with Callable and type arguments you’re uncertain about with Union, specifically Optional for working with None. Mypy is a new tool that finds errors in your code, helping you prevent failure before it happens.

To be continued

When we return in part two, we will set up Continuous Integration with Travis-CI, to automatically run type-checking in the cloud every time you push changes.

--

--