3.4. Type Annotation Callable

  • 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

3.4.1. Return

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

3.4.2. Required Parameters

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

3.4.3. Optional Parameters

>>> def add(a: int = 1, b: int = 1):
...     return a + b

3.4.4. Union

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

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

3.4.5. Alias

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

>>> number = int | float
>>>
>>> def add(a: number, b: number) -> number:
...     return a + b

3.4.6. Optional

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

>>> def say_hello(name: str | None) -> str | None:
...     if name is not None:
...         return f'Hello {name}'

3.4.7. Exception

>>> def on_timeout() -> Exception:
...     raise TimeoutError
>>> def on_timeout() -> TimeoutError:
...     raise TimeoutError

3.4.8. Literal

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

Usage:

>>> open('data.csv', mode='w')  # mypy: OK
>>> open('data.csv', mode='r')  # mypy: OK
>>> open('data.csv', mode='a')  # mypy: OK
>>> open('data.csv', mode='x')  # mypy: ERROR

3.4.9. Callable

>>> from typing import Callable
>>>
>>>
>>> def run(func: Callable[[int, int], float]):
...     ...

3.4.10. Iterator

>>> from typing import Iterator
>>>
>>>
>>> def fib(n: int) -> Iterator[int]:
...     a, b = 0, 1
...     while a < n:
...         yield a
...         a, b = b, a + b

3.4.11. Annotations

>>> def add(a: int, b: int) -> int:
...     return a + b
>>>
>>>
>>> add.__annotations__
{'a': <class 'int'>, 'b': <class 'int'>, 'return': <class 'int'>}

3.4.12. Errors

  • Python will execute without even warning

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

>>> def add(a: int, b: int) -> int:
...     return a + b
>>>
>>>
>>> add('Mark', 'Watney')
'MarkWatney'

3.4.13. Good Engineering Practices

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

3.4.14. Future

  • Since Python 3.11: PEP 563 -- Postponed Evaluation of Annotations

  • Since Python 3.11: PEP 675 -- Arbitrary Literal String Type

Postponed Evaluation of Annotations:

>>> def add(a: int, b: int) -> int:
...     return a + b
>>>
>>> 
... add.__annotations__
{'a': 'int', 'b': 'int', 'return': 'int'}

Arbitrary Literal String Type:

>>> 
... def run_query(sql: LiteralString) -> ...
...     ...
...
... def caller(
...         arbitrary_string: str,
...         query_string: LiteralString,
...         table_name: LiteralString,
...         ) -> None:
...     run_query("SELECT * FROM students")       # ok
...     run_query(query_string)                   # ok
...     run_query("SELECT * FROM " + table_name)  # ok
...     run_query(arbitrary_string)               # type checker error
...     run_query(                                # type checker error
...         f"SELECT * FROM students WHERE name = {arbitrary_string}"
...     )

3.4.15. Use Case - 0x01

>>> def valid_email(email: str) -> str | Exception:
...     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

3.4.16. Use Case - 0x02

>>> def find(text: str, what: str) -> int | None:
...     position = text.find(what)
...     if position == -1:
...         return None
...     else:
...         return position
>>>
>>>
>>> find('Python', 'x')
>>> find('Python', 'o')
4

3.4.17. Use Case - 0x03

>>> from typing import Any
>>> import requests
>>>
>>>
>>> Result = requests.Response
>>>
>>> def fetch(url: str,
...           on_success: Callable[[Result], Any],
...           on_error: Callable[[Exception], Any],
...           ) -> None:
...     try:
...         result: Result = requests.get(url)
...     except Exception as err:
...         on_error(err)
...     else:
...         on_success(result)

3.4.18. Further Reading