In a previous post, I explained how to use type annotations in Python. You can find the post here.

Now, we will see how to migrate code without type annotations using two tools: pyannotate and MonkeyType

pyannotate

Pyannotate is a project started by Dropbox and published in Github.

It is intended to be used alongside your test scripts. It requires minor modifications to your existing code to add start and stop statements. The statements mentioned will indicate where pyannotate should record the types.

The tool works in two steps: First, it will monitor the execution of the tests and record the types, and the second step is to write the type into our existing code.

Pyannotate allows us to use python annotations or to save the type as comments in our source code. The last option is handy if we are dealing with legacy code in Python 2

Example

We need to install the package: pip install pyannotate

I will reuse the example of my previous post regarding python annotations:

def print_me(message):
    '''
    This function prints a message
    '''
    world_string = 'World'
    print(message + ' ' + world_string)

print_me('Hello')

I will modify the code above to include the calls required by pyannotate to collect the data:

from pyannotate_runtime import collect_types
collect_types.init_types_collection()


def print_me(message):
    '''
    This function prints a message
    '''
    world_string = 'World'
    print(message + ' ' + world_string)


collect_types.start()

print_me('Hello')

collect_types.stop()
collect_types.dump_stats('example-types.dump')

After running our program python example.py, we should see a new file generated in the same folder: example-types.dump. The file contains the information about the type annotations.

Finally, we can run: pyannotate --type-info example-types.dump --py3 -w example.py. The example file is modified to include the type annotations in the print_me function.

[...]
def print_me(message: str) -> None:
[...]

MonkeyType

MonkeyType was developed by Instagram and released in Github.

The difference with pyannotate is that we don’t need to modify our existing code. Instead, call our program running monkeytype, replacing the usual call to our python interpreter.

As pyannotate, MonkeyType also requires two steps: A first step to collect the types and a second step to apply the steps. The tool allows us to modify our code directly with the detected types or create python stubs (.ipyi files).

Python stub contains information about the signatures of the functions but not the implementation. So from that point of view, it is very similar to .h files (if you are familiar with C++).

Example

First, let’s proceed to install the program: pip install monkeytype

Again, I will reuse the previous example, but I will save the logic as a module (as monkeytype operates with modules)

My module (print_me/__init__.py)

def print_me(message):
    '''
    This function prints a message
    '''
    world_string = 'World'
    print(message + ' ' + world_string)

My example (example.py):

import print_me

print_me.print_me('Hello')

We can run our program, but using monkeytype rather than Python: monkeytype run example.py. After running the program, you will see a monkeytype.sqlite3. The file is the database with the information of annotations type obtained during the execution of the example.

Last, we can apply the types to our existing code running: monkeytype apply print_me, and we can see the changes in the print_me/__init__.py file:

[...]
def print_me(message: str) -> None:
[...]

Conclusion

We have studied two tools that will allow us to migrate code automatically with type annotations. The advantage, in the long term, is to avoid bugs and make our code mode readable. Of course, it is wise to use these tools with a revision control system to monitor the changes made automatically and adjust if necessary.