Type annotations#

As we have talked about in earlier lectures, Python is a dynamically language, this means that a variable does not have to have a single type throughout a program. For instance, the following is valid:

x = "42"
print(type(x))
x = 42.0
print(type(x))
x = 42
print(type(x))
<class 'str'>
<class 'float'>
<class 'int'>

In PEP484 (Python Enchancement Proposal 484) it was suggested to add type hints to Python, allowing users to annotate their code with hints at to what type an input or output variable of a function should have. However, these annotations are not checked at run-time, and third-party libraries such as mypy exist for static type-checking.

We can annotate a variable with a type hint by using a “colon” (:)

a: int  # Declare that we expect a to be an integer
b: int = 5  # Declare that we expect an integer and set it to 5

However, as the language is not statically typed, we can still assign other types to the variables

a = "s"  # No error
b = [11.0, 15.0, 13.0]  # No error

We can use type-hints for all built in types in Python

c: list[str] = ["a", "b", "c"]

For older versions of Python (<3.9), one have to import List from the typing-module

from typing import List

c: List[str]

Type hints for functions#

We can use type hints for each input argument of a function by declaring the type after a colon. The return type is declared after the function brackets by using -> followed by the type before starting the function block.

def triple(x: float) -> float:
    print(f"Computing 3 * {x}")
    return 3.0 * x

We can use typing.Callable to give type definititons for a function.

from typing import Callable


def half(x: float) -> float:
    print(f"Computing 0.5 * {x}")
    return 0.5 * x


def f_of_g(f: Callable[[float], float], g: Callable[[float], float], x: float) -> float:
    return f(g(x))


print(f"Computed: {f_of_g(triple, half, 10)}")
Computing 0.5 * 10
Computing 3 * 5.0
Computed: 15.0

Union types#

Some times a function can take in an argument that can have multiple types, we use the typing.Union[type_x, type_y,...] operator (for Python < 3.10) or the | operator in (Python >= 3.10) for this

def user_id(name: str, year: int | str):
    if isinstance(year, str):
        return name + year
    elif isinstance(year, int):
        return name + str(year)
    else:
        raise ValueError(f"Unsupported input: {type(year)=}.")
user_id("James", 1705)
'James1705'
user_id("James", "1992")
'James1992'

Other types of importance#

  • typing.Optional - Used when an input argument to a function is optional (i.e. default value in function is None). Equivalent to Union[type_x, None].

  • typing.Dict[x, y]. In Python >= 3.9 one can use dict[x, y] to decleare that all keys of a dictionary is of type x, while all values are of type y.

  • typing.Protocol - Static duck typing, PEP544. Used to make sure that type-checkers can work with the duck-typing priciple.

Resources#