Skip to content

#207: Type Hints

Internally Python uses classes and therefore types everywhere. However, when we declare our methods, we omit the types and end up with limited functionality for our code editors. In this post we look at type hints and how they can improve our experience to write code.

Type hints?

Python offers type hints since version 3.5 and they are meant to be hints:

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.

The basic example from the documentation uses this code snipped to show you the syntax:

def greeting(name: str) -> str:
    return 'Hello ' + name

The greetings() function accepts the parameter name of the type string and returns a string.

The benefits

If we add the example from above with the type hints into a module called with_types.py, we get this support in VS Code:

VS Code shows us all the methods we can call on a string.

However, if we put our method from above into a module called without_types.py and omit the type hints, we get only this support in VS Code because it knows nothing about a being a string:

Not much support from VS Code, then it does not know what type we get back.

What types can we use?

The "Type hints cheat sheet" offers examples for most use cases. I only picked the ones I find the most useful for Python 3.11+:

from typing import Callable, Iterator, Union, Optional, Any

# one parameter with a type
def stringify(num: int) -> str:
    return str(num)


# multiple parameters with types
def plus(num1: int, num2: int) -> int:
    return num1 + num2


# return type can differ from types of parameters
def division(num1: int, num2: int) -> float:
    return num1 / num2


# lists can have a type
def total(values: list[int]) -> int:
    return sum(values)


# for dictionaries we can specify the type of the key and of the value
def total_values(input: dict[str, float]) -> float:
    return sum(input.values())


# touples with variable size but same type can use ...
def print_tuple(numbers: tuple[int, ...]) -> None:
    for number in numbers:
        print(number)


# booleans can be used as type 
def is_working(a: bool, b: bool) -> bool:
    return a & b

How to handle special cases?

If our parameter could hold a string or None, we can declare it with the Optional keyword:

1
2
3
def hello(name: Optional[str]) -> None:
    if name:
        print(name)

If we can accept multiple types, we can use the | character to separate the different type hints:

1
2
3
def printer(values: list[int | str]) -> None:
    for i in values:
        print(i)

If you do not care about the type but you need to define something, you can use Any -> None:

def whatever(input: Any):
    pass

Conclusion

Type hints are optional and only hint at the type. They offer us some great help while we write code and make it clearer what type we expect in our functions. There are tools like Pydantic that leverage the type hints and give us validation and fitting error messages for no additional cost. We are revisiting this topic when we explore FastAPI, a framework that is built on top of type hints.