All posts

Advanced type annotations using Python's TypeVar

Type annotations are very common now in the Python world.

The typing module has a lot of powerful features, and in this article we'll explore TypeVar, which is essential for annotating certain functions correctly.

Simple annotations

A simple type annotated function is shown below:

def get_message(name: str) -> str:
    return f'Hello {name}'

We pass in a string, and return a string - nice and easy.

Advanced annotations using TypeVar

There are some situations where we have to get more creative with our type annotations. Consider the function below, which doubles the number we pass into it:

def double(value: int | float | decimal.Decimal):
    return value * 2

Several value types are allowed (int, float and Decimal). We could add the following return type:

def double(
    value: int | float | decimal.Decimal
) -> int | float | decimal.Decimal:
    return value * 2

But when you think about it, it doesn't really make sense. When we pass in an int, we should get an int returned. What this type annotation is saying is that when we pass in an int, then we could get back an int, float or Decimal.

This is where TypeVar comes in. It allows us to do this:

import decimal
from typing import TypeVar

Number = TypeVar("Number", int, float, decimal.Decimal)

def double(value: Number) -> Number:
    return value * 2

This tells static analysis tools like mypy and Pylance that the type returned by the function is the same as the type which was passed in.

It also tells the type checker that values other than int, float and Decimal aren't allowed:

double("hello")  # error

Piccolo uses TypeVar extensively - without it, it would be impossible to provide correct types for certain functions. Give it a go!

Posted on: 7 Jan 2023

Have any comments or feedback on this post? Chat with us on GitHub.