2.4. Function Type Annotation

2.4.1. Rationale

  • Before Python 3.9 you need from typing import List, Set, Tuple, Dict

  • Since Python 3.9: PEP 585 -- Type Hinting Generics In Standard Collections

2.4.2. Return

>>> def say_hello() -> str:
...     return 'My name... José Jiménez'

2.4.3. Parameters

def add_numbers(a: int, b: int) -> int:
    return a + b

add_numbers(1, 2)
# 3

2.4.4. Union

from typing import Union


def add_numbers(a: Union[int,float], b: Union[int,float]) -> Union[int,float]:
    return a + b


add_numbers(1, 2)       # 'Ok'
add_numbers(1.5, 2.5)   # 'Ok'
from typing import Union

Number = Union[int, float]

def add_numbers(a: Number, b: Number) -> Number:
    return a + b

add_numbers(1, 2)       # 'Ok'
add_numbers(1.5, 2.5)   # 'Ok'

Since Python 3.10: PEP 604 -- Allow writing union types as X | Y

def add_numbers(a: int|float, b: int|float) -> int|float:
    return a + b


add_numbers(1, 2)       # 'Ok'
add_numbers(1.5, 2.5)   # 'Ok'

2.4.5. Optional

from typing import Union


def find(text: str, what: str) -> Union[int,None]:
    position = text.find(what)

    if position == -1:
        return None
    else:
        return position


find('Python', 'o')      # 4
find('Python', 'x')      # None
from typing import Optional


def find(text: str, what: str) -> Optional[int]:
    position = text.find(what)

    if position == -1:
        return None
    else:
        return position


find('Python', 'o')      # 4
find('Python', 'x')      # None

Since Python 3.10: PEP 604 -- Allow writing union types as X | Y

def find(text: str, what: str) -> int|None:
    position = text.find(what)

    if position == -1:
        return None
    else:
        return position


find('Python', 'o')      # 4
find('Python', 'x')      # None

Since Python 3.10: PEP 645 -- Allow writing optional types as x?

def find(text: str, what: str) -> int?:
    position = text.find(what)

    if position == -1:
        return None
    else:
        return position


find('Python', 'o')      # 4
find('Python', 'x')      # None

2.4.6. NoReturn

from typing import NoReturn


def stop() -> NoReturn:
    raise RuntimeError
from typing import Union, NoReturn


def valid_email(email: str) -> Union[NoReturn, str]:
    if '@' in email:
        return email
    else:
        raise ValueError('Invalid Email')


valid_email('mark.watney@nasa.gov')
# 'mark.watney@nasa.gov'

valid_email('mark.watney_at_nasa.gov')
# Traceback (most recent call last):
# ValueError: Invalid Email

2.4.7. Literal

  • Since Python 3.8: PEP 586 -- Literal Types

from typing import Literal


def open(filename: str, mode: Literal['r','w','a']) -> None:
    pass

open('data.csv', mode='w')  # Ok
open('data.csv', mode='r')  # Ok
open('data.csv', mode='a')  # Ok
open('data.csv', mode='x')  # Error

2.4.8. Annotations

def add(a: int, b: int) -> int:
    return a + b


add.__annotations__
# {'a': <class 'int'>,
#  'b': <class 'int'>,
#  'return': <class 'int'>}

Since Python 3.10: PEP 563 -- Postponed Evaluation of Annotations

def add(a: int, b: int) -> int:
    return a + b


add.__annotations__
# {'a': 'int',
#  'b': 'int',
#  'return': 'int'}

2.4.9. Errors

  • Python will execute without even warning

  • Your IDE and mypy et. al. will yield errors

def add_numbers(a: int, b: int) -> int:
    return a + b


add_numbers('Mark', 'Watney')
# 'MarkWatney'

2.4.10. Good Engineering Practices

from typing import Union

def add_numbers(a: Union[int,float],
                b: Union[int,float]
                ) -> Union[int,float]:
    return a + b

add_numbers(1, 2)       # 'Ok'
add_numbers(1.5, 2.5)   # 'Ok'

2.4.11. Further Reading