3.1. Typing About

  • Also known as: "type annotations", "type hints", "gradual typing"

  • Types are not required, and never will be

  • Good IDE will give you hints

  • Types are used extensively in system libraries

  • More and more books and documentations use types

  • Introduced in Python 3.5

  • To type check use: mypy, pyre-check, pytypes

  • https://typing.readthedocs.io/en/latest/

Types are not required, and never will be. -- Guido van Rossum, Python initiator, core developer, former BDFL

It should be emphasized that Python will remain a dynamically typed language, and the authors have no desire to ever make type hints mandatory, even by convention. -- Python Software Foundation

../../_images/typeannotation-timeline.png

Figure 3.2. Timeline of changes to type annotations from Python 3.0 to now [1]

3.1.1. Problem

>>> def add_numbers(a, b):
...     return a + b
>>>
>>>
>>> add_numbers(1, 2)
3
>>> add_numbers(1.0, 2.0)
3.0
>>> add_numbers('a', 'b')
'ab'
>>> add_numbers(['a'], ['b'])
['a', 'b']

3.1.2. Solution 1

>>> def add_numbers(a, b):
...     return a + b
>>>
>>>
>>> add_numbers(1, 2)
3
>>> add_numbers(1.0, 2.0)
3.0
>>> add_numbers('a', 'b')
'ab'
>>> add_numbers(['a'], ['b'])
['a', 'b']

3.1.3. Solution 2

>>> def add_numbers(a: int|float, b: int|float) -> int|float:
...     return a + b
>>>
>>>
>>> add_numbers(1, 2)
3
>>> add_numbers(1.0, 2.0)
3.0
>>> add_numbers('a', 'b')
'ab'
>>> add_numbers(['a'], ['b'])
['a', 'b']

3.1.4. Python 2.X

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

3.1.5. Python 2.7

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

3.1.6. Python 3.0

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

3.1.7. Python 3.5

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

3.1.8. Typing PEPs

  • https://peps.python.org/topic/typing

  • Since Python 3.5: PEP 484 -- Type Hints

  • Since Python 3.6: PEP 526 -- Syntax for Variable Annotations

  • Since Python 3.8: PEP 544 -- Protocols: Structural subtyping (static duck typing)

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

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

  • PEP 482 - literature overview on type hints

  • PEP 483 - background on type hints

  • PEP 484 - type hints

  • PEP 526 - variable annotations and ClassVar

  • PEP 544 - Protocol

  • PEP 561 - distributing typed packages

  • PEP 563 - from __future__ import annotations

  • PEP 585 - subscriptable generics in the standard library

  • PEP 586 - Literal

  • PEP 589 - TypedDict

  • PEP 591 - Final

  • PEP 593 - Annotated

  • PEP 604 - union syntax with |

  • PEP 612 - ParamSpec

  • PEP 613 - TypeAlias

  • PEP 646 - variadic generics and TypeVarTuple

  • PEP 647 - TypeGuard

  • PEP 649 - (draft), from __future__ import co_annotations

  • PEP 655 - Required and NotRequired

  • PEP 673 - Self

  • PEP 675 - LiteralString

  • PEP 677 - (rejected), (int, str) -> bool callable type syntax

  • PEP 681 - @dataclass_transform()

  • PEP 688 - Buffer

  • PEP 692 - Unpack[TypedDict] for **kwargs

  • PEP 695 - class Class[T]: type parameter syntax

  • PEP 696 - (draft), defaults for type variables

  • PEP 698 - @override

  • PEP 702 - (draft), @deprecated()

  • PEP 705 - (draft), TypedMapping

3.1.9. Errors

  • Types are not Enforced

  • This code will run without any problems

  • Types are not required, and never will be

  • Although mypy, pyre-check or pytypes will throw error

>>> def add(a: int, b: int) -> int:
...     return a + b
>>>
>>>
>>> add(1, 2)
3
>>>
>>> add(1.0, 2.0)
3.0
>>>
>>> add('a', 'b')
'ab'

3.1.10. Annotation

  • Annotation and definition can be separated

Two step process:

>>> x: int
>>> x = 1

One liner:

>>> x: int = 1

3.1.11. Annotation is not Definition

Two step process:

>>> print(a)
Traceback (most recent call last):
NameError: name 'a' is not defined
>>>
>>> a: int
>>>
>>> print(a)
Traceback (most recent call last):
NameError: name 'a' is not defined
>>>
>>> a = 1
>>> print(a)
1

Oneliner:

>>> print(b)
Traceback (most recent call last):
NameError: name 'b' is not defined
>>>
>>> b: int = 1
>>>
>>> print(b)
1

Even if the type is wrong, the code will run and the value will be assigned:

>>> print(c)
Traceback (most recent call last):
NameError: name 'c' is not defined
>>>
>>> c: int = 'one'
>>>
>>> print(c)
one

3.1.12. Dynamic Typing

  • good: fast development

  • bad: runtime errors (in projects with many developers)

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

3.1.13. Static Typing

  • good: no runtime errors

  • bad: slow development

int add(int a, int b) {
    return a + b;
}

float add(float a, float b) {
    return a + b;
}

float add(int a, float b) {
    return (float)a + b;
}

float add(float a, int b) {
    return a + (float)b;
}

3.1.14. Gradual Typing

  • good: fast development

  • good: no runtime errors

Start:

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

Later:

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

Later:

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

Later:

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

3.1.15. Checkers

  • mypy - the reference implementation for type checkers

  • pyre - written in OCaml and optimized for performance

  • pyright - a type checker that emphasizes speed

  • pytype - checks and infers types for unannotated code

3.1.16. Further Reading

3.1.17. References