Type Hinting and Annotations in Python

Type Hints Annotations Python

Python is a dynamically typed language. We don’t have to explicitly mention the data types for the declared variables or functions. Python interpreter assigns the type for variables at the runtime based on the variable’s value at that time. We also have statically typed languages like Java, C, or C++, where we need to declare the variable type at the time of declaration and the variable types are known at compile time.

Starting from Python 3.5 there was an introduction of something called type hints in PEP 484 and PEP 483. This addition to the Python language helped structure our code and make it feel more like a statically typed language. This helps to avoid bugs but at the same time makes the code more verbose.

However, the Python runtime does not enforce function and variable type annotations. They can be used by third-party tools such as type checkers, IDEs, linters, etc.

Also read: The Magic Methods in Python

Type Checking, Type Hints, and Code Compilation

At first, we had some external third-party libraries for example the static type checker like mypy that started doing type hinting, and a lot of those ideas from mypy were actually brought into the canonical Python itself and integrated directly into Python.

Now, the thing with type hints is that it does not modify how Python itself runs. The type hints do get compiled along with the rest of the code but they do not affect how Python executes your code.

Let’s go through an example and get an overview by assigning type hints to a function.

Code:

def multiply(num1: int, num2: int):
    return num1 * num2

print(multiply(5, 7))  # 35
print(multiply(5, "Hi"))  # HiHiHiHiHi

Explanation:

In the function declared above, we are assigning built-in data types to the arguments. It’s the good old normal function but the syntax here is a bit different. We can note that the arguments have a semicolon with a data type assigned to them (num1: int, num2: int)

This function is taking two arguments num1 and num2, that’s what Python sees when it’s going to run the code. It is expecting two variables. Python would have been just fine even if we do not put any type hints that we specified saying that num1 and num2 should be integers.

So according to it, we should be passing two integer values to our code and that would work fine. However, what if we try to pass an integer and a string?

Type Hinting tells us to pass in int values, yet we are passing a str. When we try to run the code, it runs fine with no issues. Python interpreter has no problem compiling the code if there is a valid data type present in out type hints like int, str, dict, and so on.

Why use Type Hinting at all?

In the example above, we saw that the code runs fine even if we pass a string value to it. Python has no problems multiplying an int with str. However, there are some really good reasons to use type hints even if Python ignores them.

  • One of the things is that it helps IDEs display context-sensitive help information such as not only the function parameters but also what the expected type is.
  • Type hints are often used for code documentation. There are multiple automated code document generators that use type hints when they generate the documentation for example if we are writing our own code libraries with lots of functions and classes and also the included comments.
  • Even if Python does not use type hinting at all, it helps us to leverage it to use a more declarative approach while writing our code and also to provide a runtime validation using external libraries.

Using a Type Checker

There are several type checkers for Python. One of them is the mypy.

Let’s use the same code that we ran before using an int and str. We will use the static type checker mypy and see what Python has to say about our code.

  • Installing mypy
pip install mypy
  • Code with Type Hints using a type checker while running the code

Code:

def multiply(num1: int, num2: int):
    return num1 * num2

print(multiply(5, "Hi"))

In the terminal run the file with the type checker prefixed:

mypy type-hints.py

Output:

type-hints.py:9: error: Argument 2 to "multiply" has incompatible type "str"; expected "int"
Found 1 error in 1 file (checked 1 source file)

Explanation:

While we run our file with the type checker, now the Python interpreter has a problem with our code. The expected argument is an int data type for both the arguments and we are passing a string in one of them. The type checker tracked the bug and shows that in our output. The mypy type checker helped us address the problem in our code.

More Examples of Type Hinting in Python

In the above example, we used int and str types while hinting. Similarly, other data types can also be used for type hinting as well. We can also declare a type hint for the return type in a function.

Let’s go through the code and see some examples.

Example: 1

def some_function(a: int, b: float, c: dict, d: bool = False):
    pass

Explanation:

Here we are type hinting at different data types for our arguments. Note that we can also assign default values to our parameters if there is no argument provided.

Example: 2

def person(name: str, age: int) -> str:
    return f"{name} is a {age} year old programmer"

Explanation:

In the above code, the return type has also been declared. When we try to run the code using a type checker like mypy, Python will have no problems running the code as we have a string as the return type, which matches with the type hint provided.

Example: 3

def other_function(name: str, age: int) -> None:
    return f"{name} is a {age} year old programmer"

Explanation:

This code has a return type of None. When we try to run this code using the mypy type checker, Python will raise an exception, since it is expecting a return type of None, and the code is returning a string.

Example: 4

my_list: list = ["apple", "orange", "mango"]
my_tuple: tuple = ("Hello", "Friend")
my_dictionary: dict = {"name": "Peter", "age": 30}
my_set: set = {1, 4, 6, 7}

Explanation:

The above code shows type hints which are usually referred to as Variable Annotations. Just like we provided type hints to our functions in the above examples, even the variables can hold similar information and help make code more declarative and documented.

The typing Module

A lot of times we have more advanced or more complicated types that have to be passed as an argument to a function. Python has a built-in typing module that enables us to write such types of annotated variables, making the code even more documented. We have to import the typing module to our file and then use those functions. These include data structures such as List, Dictionary, Set, and Tuple.

Let’s see the code a get an overview along with the comments as explanations.

from typing import List, Dict, Set, Tuple, Union

# Declaring a nested list with annotations
my_new_list: List[List[int]] = [[1, 2, 3], [4, 5]]

# Declaring a dictionary with keys as strings and values as integers
my_new_dict: Dict[str, int] = {"age": 34, "languages": 2}

# Declaring a set with only string values
my_new_set: Set[str] = {"apple", "mango"}	

# Declaring a tuple with exactly 3 parameters with string values only
my_new_tuple: Tuple[str, str, str] = ("Hello", "There", "Friend")

# Declaring a list that may hold integers, floats, and strings all at once
my_union_list: List[Union[int, float, str]] = [12, "notebook", 34.56, "pencil", 78]

'''
Since Python 3.10.x we can use the pipe | character 
instead of importing typing Module for using Union
'''
def func(a: str | int | float, b: int) -> str | int | float:
    return a * b

Summary

There are numerous other ways to utilize type hints in Python. Using types does not affect the performance of the code and we are not getting any extra functionalities as well. However, using type hints provides robustness to our code and provides documentation for people who would read the code later.

It certainly helps to avoid introducing difficult-to-find bugs. Using types while writing code is becoming popular in the current technology scenario and Python is also following the pattern by providing us with easy-to-use functions for the same. For more information, please refer to the official documentation.

Reference

Python Type Hints Documentation