1. Type Annotation

1.1. What are Type Annotations?

  • Also known as Type Hinting

  • Introduced in Python 3.5 with PEP 484

  • Accessed by __annotations__ attribute

  • No type checking happens at runtime

  • Use separate off-line type checker which users can run over their source code voluntarily

  • Supports unions, generic types, and a special type named

  • Any is consistent with (i.e. assignable to and from) all types

  • This latter feature is taken from the idea of gradual typing.

  • Gradual typing and the full type system are explained in PEP 483

Warning

It should also 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.

1.2. Data types

name: str = 'Jan Twardowski'
leet: int = 1337
pi: float = 3.14
def add(a: int, b: float) -> float:
    return a + b


add(1, 2.5)

1.3. Sequences

1.3.1. List

from typing import List


my_list: list = ['a', 2, 3.3]

my_list: List[int] = [1, 2, 3]
my_list: List[float] = [1.1, 2.2, 3.3]
my_list: List[str] = ['a', 'b', 'c']

1.3.2. Set

from typing import Set


my_set: set = {'a', 2, 3.3}

my_set: Set[int] = {1, 2, 3}
my_set: Set[float] = {1.1, 2.2, 3.3}
my_set: Set[str] = {'a', 'b', 'c'}

1.3.3. Tuple

from typing import Tuple


my_tuple: tuple = 'a', 2, 3.3
my_tuple: tuple = ('a', 2, 3.3)

my_tuple: Tuple[int, int, int] = (1, 2, 3)
my_tuple: Tuple[float, float, float] = (1.1, 2.2, 3.3)
my_tuple: Tuple[str, str, str] = ('a', 'b', 'c')

my_tuple: Tuple[str, int, float] = ('a', 2, 3.3)

1.3.4. Dict

from typing import Dict


my_dict: dict = {
    'a': 1,
    2: 'b',
    3.3: 'c',
}

my_dict: Dict[str, int] = {
    'a': 1,
    'b': 2,
    'c': 3,
}

1.4. Nested sequences

1.4.1. List of dict

from typing import List, Dict


list_of_dicts: List[dict] = [
    {'a': 1},
    {2: 'b'},
    {3.3: 'c'}
]

list_of_dicts: List[Dict[str, int]] = [
    {'a': 1},
    {'b': 2},
    {'c': 3},
]

1.4.2. List of tuples

from typing import List, Tuple


my_data: List[tuple] = [
    (1, 2, 3),
    (1.1, 2.2, 3.3),
    ('a', 'b', 'c'),
    ('a', 2, 3.3),
]

my_data: List[Tuple[int, int, int]] = [
    (1, 2, 3),
    (1, 2, 3),
    (1, 2, 3),
]

1.5. Union

from typing import Union


def round(number: Union[int, float]) -> int:
    return int(number)
from typing import Union


def handle_employees(e: Union[Employee, Sequence[Employee]]) -> None:
    if isinstance(e, Employee):
        e = [e]
from typing import Union


AllowedTypes = Union[list, set, tuple]

def my_print(args: AllowedTypes) -> None:
    if not isinstance(args, AllowedTypes.__args__):
        raise TypeError(f'Collection must be instance of {AllowedTypes.__args__}')

    for element in collection:
        print(element)

1.6. Any

from typing import Any


def my_print(value: Any) -> None:
    print(value)

1.7. Optional

from typing import Optional


def non_zero(number: int) -> Optional[int]:
    if not number:
        return None
    else:
        return number

1.8. The NoReturn type

from typing import NoReturn


def stop() -> NoReturn:
    raise RuntimeError

1.9. Type aliases

from typing import List, Tuple


GeographicCoordinate = Tuple[float, float]

locations: List[GeographicCoordinate] = [
    (25.91375, -60.15503),
    (-11.01983, -166.48477),
    (-11.01983, -166.48477)
]

1.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

1.11. Overload

  • The @overload decorator allows describing functions and methods that support multiple different combinations of argument types.

  • A series of @overload-decorated definitions must be followed by exactly one non-@overload-decorated definition (for the same function/method)

  • The @overload-decorated definitions are for the benefit of the type checker only, since they will be overwritten by the non-@overload-decorated definition

@overload
def process(response: None) -> None:
    ...

@overload
def process(response: int) -> Tuple[int, str]:
    ...

@overload
def process(response: bytes) -> str:
    ...

def process(response):
    <actual implementation>

1.12. Final

from typing import final

@final
class Base:
    ...

class Derived(Base):  # Error: Cannot inherit from final class "Base"
    ...
from typing import final

class Base:
    @final
    def foo(self) -> None:
        ...

class Derived(Base):
    def foo(self) -> None:  # Error: Cannot override final attribute "foo"
                            # (previously declared in base class "Base")
        ...
from typing import Final


ID: Final = 1
ID: Final[float] = 1
from typing import Final

class Window:
    BORDER_WIDTH: Final = 2.5

class ListView(Window):
    BORDER_WIDTH = 3  # Error: can't override a final attribute
from typing import Final

class ImmutablePoint:
    x: Final[int]
    y: Final[int]  # Error: final attribute without an initializer

    def __init__(self) -> None:
        self.x = 1  # Good
from typing import Final

RATE: Final = 3000

class Base:
    DEFAULT_ID: Final = 0

RATE = 300  # Error: can't assign to final attribute
Base.DEFAULT_ID = 1  # Error: can't override a final attribute

1.13. Literal

from typing import Literal

def accepts_only_four(x: Literal[4]) -> None:
    pass

accepts_only_four(4)   # OK
accepts_only_four(19)  # Rejected
from typing import Literal, overload


@overload
def open(path: str,
         mode: Literal["r", "w", "a", "x", "r+", "w+", "a+", "x+"],
         ) -> IO[str]: ...

@overload
def open(path: str,
         mode: Literal["rb", "wb", "ab", "xb", "r+b", "w+b", "a+b", "x+b"],
         ) -> IO[bytes]: ...

1.14. TypedDict

from typing import TypedDict


class Movie(TypedDict):
    name: str
    year: int


movie: Movie = {
    'name': 'Blade Runner',
    'year': 1982
}

def record_movie(movie: Movie) -> None:
    ...

record_movie({'name': 'Blade Runner', 'year': 1982})
Listing 278. The code below should be rejected, since 'title' is not a valid key, and the 'name' key is missing
from typing import TypedDict


class Movie(TypedDict):
    name: str
    year: int

movie2: Movie = {
    'title': 'Blade Runner',
    'year': 1982
}
from typing import TypedDict


class Movie(TypedDict):
    name: str
    year: int

m = Movie(name='Blade Runner', year=1982)
from typing import TypedDict


class Movie(TypedDict):
    name: str
    year: int

m: Movie = dict(
    name='Alien',
    year=1979,
    director='Ridley Scott')  # error: Unexpected key 'director'
from typing import TypedDict


class Movie(TypedDict):
    name: str
    year: int

class BookBasedMovie(Movie):
    based_on: str
from typing import TypedDict


class X(TypedDict):
    x: int

class Y(TypedDict):
    y: str

class XYZ(X, Y):
    z: bool

1.15. TypeVar, Iterable, Tuple

from typing import TypeVar, Iterable, Tuple

T = TypeVar('T', int, float, complex)
Vector = Iterable[Tuple[T, T]]

def inproduct(v: Vector[T]) -> T:
    return sum(x*y for x, y in v)

def dilate(v: Vector[T], scale: T) -> Vector[T]:
    return ((x * scale, y * scale) for x, y in v)

vec = []  # type: Vector[float]

1.16. Callable

from typing import Callable

def feeder(get_next_item: Callable[[], str]) -> None:
    pass

def async_query(on_success: Callable[[int], None],
                on_error: Callable[[int, Exception], None]) -> None:
    pass