Codementor Events

Why you should use type annotations in Python (some of the time)

Published Jun 06, 2018Last updated Dec 03, 2018
Why you should use type annotations in Python (some of the time)

Python 3.5 introduced type annotations, or type hints, as proposed in PEP-483 and PEP-484.

In case you're unfamiliar with type annotations, it looks like this:

def say_hello(name: str) -> None:
    print("Hello " + name)

In the function definition, we specify that name should be a str and that we expect that nothing will be returned. If you're familiar with Java, this would be somewhat equivalent (see below):

void sayHello(String name)  { 
   System.out.println("Hello " + name);
}

It is important to note that type annotations are merely that: annotations. They do not turn Python into a statically typed language like Java. For example:

>>> def say_hello(name: int) -> None:
...     print("Hello " + name)
...
>>> say_hello("Ben")
Hello Ben

The function's type annotation shows that say_hello expects an int, but when I provide a str, it will run as expected. If I did something similar in Java:

java> void sayHello(int name)  {     System.out.println("Hello " + name); }
Created method void sayHello(int)
java> sayHello("Ben")
ERROR: incompatible types: java.lang.String cannot be converted to int
    sayHello("Ben");

it does of course complain. I will not go into depth about the advantages and disadvantages of statically-typed vs dynamically-typed languages here, as this would merit its own post.

It is important however to realize type annotations are essentially just fancier comments. They do not affect the Python interpreter's basic functioning in any way. Python does not check whether the arguments you provide match the type specified in the type annotation.

Getting too caught up with types would be unpythonic, as Python has a concept called 'duck typing'. It means that if an object implements a certain method, then we just use that method, regardless of the type of that object. We will often use a try-except block instead of explicitly checking whether an object is an instance of a certain class. This is called being type-agnostic. For example:

def get_name(foo):
try:
    return foo.name
except AttributeError:
    return None

This function can work with any foo that has a name attribute. We don't care about the type of foo, just that it has a name.

Why do I advocate for type annotations then? There are a number of reasons.

First, it forces you to think about what a function does at a higher level. This is something that can be incredibly valuable, which I learned during my time learning Haskell. While Haskell does not require you to specify the types of a function (like in Java), its syntax does really make it very clear what your function is doing, even without the actual function definition.

Secondly, it can make code much more readable. To give an example:

def get_title(program):
    return program.find("h3", {"class": "program-full__title"}).text.strip()

What is program? Is it a string? A custom object? I don't know. However:

def get_title(program: BeautifulSoup) -> str:
    return program.find("h3", {"class": "program-full__title"}).text.strip()

Now it's entirely clear what program is supposed to be. It's a BeautifulSoup object that implements a find method. Without type annotations, it would take a long time to figure out what program was supposed to be if it had been a long time since I'd worked on the code, or if the application was sufficiently large.

Third, while the standard Python interpreter doesn't check whether the argument types match those specified in the function definition, external tools and IDEs can do this. This can prevent a lot of runtime errors from occurring, and can thus make your code more reliable.

Whether you use type annotations, and how often, will depend on the kind of applications you write and your own style. Some Pythonistas are opposed to type annotations, as they consider it unpythonic. I think that being too rigorous with this rule can decrease code quality. In the example above, it really was useful to know that the program function requires a BeautifulSoup object, and I feel that it's much more appropriate than duck-typing here.

Here I get to an important point. People often mistake Python's unintimidating syntax with the language being easy. It is not. Duck typing and using try-except blocks instead of using type checking is an advanced concept that is far more difficult for beginners to understand or implement. Especially as a beginner, you should think very explicitly about types, and type annotations can help you with this.

Discover and read more posts from Ben Baert
get started
post commentsBe the first to share your opinion
Show more replies