Python type annotations

Type annotations

Since Python there is support for annotations of variable types, class fields, arguments, and return values ​​of functions. However it breaks backwards compatibility. If you want to keep it, write types in docstrings.

Basics

  • Variable annotations are written with a colon after the identifier. This can be followed by value initialization.

    1
    2
    price: int = 5
    title: "str"
  • Function parameters are annotated in the same way as variables, and the return value is specified after the arrow -> and before the trailing colon.

    1
    2
    3
    def func(a: int, b: float) -> str:
    a: str = f"{a}, {b}"
    return a
  • For class fields, annotations must be specified explicitly when the class is defined. However, analyzers can automatically infer them based on the __init__ method, but in this case, they will not be available at runtime:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Book:
    title: "str"
    author: str

    def __init__(self, title: str, author: str) -> None:
    self.title = title
    self.author = author

    b: Book = Book(title="Fahrenheit 451", author="Bradbury")

Built-in types (sub-modules)

Optional

1
2
3
4
5
6
7
amount: int
amount: None
# Gives "Incompatible types" error

price: Optional[int]
price: None
# It will work!

Any

1
2
3
4
5
6
7
8
9
10
11
12
13
# do not restrict possible types
some_item: Any = 1
print(some_item)
print(some_item.startswith("hello"))
print(some_item // 0)

# object may have some issues, avoid it
some_object: object
print(some_object)
print(some_object.startswith("hello"))
# ERROR: "object" has no attribute "startswith"
print(some_object // 0)
# ERROR: Unsupported operand types for // ("object" and "int")

Union

1
2
3
4
5
6
7
8
9
10
11
# allow only some types
def hundreds(x: Union[int, float]) -> int:
return (int(x) // 100) % 100

hundreds(100.0)
hundreds(100)
hundreds("100")
# ERROR: Argument 1 to "hundreds" has incompatible type "str";
# expected "Union[int, float]"

# Also `Optional[T]` is equivalent toUnion[T, None]

Collections

See PEP484 - Generics.

  • Generics

    1
    2
    3
    4
    5
    6
    from typing import Mapping, Set

    def notify_by_email(
    employees: Set[Employee],
    overrides: Mapping[str, str]
    ) -> None: ...
  • Generics with parameters

    1
    2
    3
    4
    5
    6
    7
    8
    from typing import Sequence, TypeVar

    # Declare type variable
    T = TypeVar('T')

    # Generic function
    def first(l: Sequence[T]) -> T:
    return l[0]
  • User defined

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    from typing import TypeVar, Generic, Iterable
    from logging import Logger

    T = TypeVar('T')

    class LoggedVar(Generic[T]):
    def __init__(self, value: T, name: str, logger: Logger) -> None:
    self.name = name
    self.logger = logger
    self.value = value

    def set(self, new: T) -> None:
    self.log('Set ' + repr(self.value))
    self.value = new

    def get(self) -> T:
    self.log('Get ' + repr(self.value))
    return self.value

    def log(self, message: str) -> None:
    self.logger.info('{}: {}'.format(self.name, message))

    def zero_all_vars(vars: Iterable[LoggedVar[int]]) -> None:
    for var in vars:
    var.set(0)

Lists

1
2
3
4
5
6
7
8
9
10
titles: List[str] = ["hello", "world"]
titles.append(100500)
# ERROR: Argument 1 to "hundreds" has incompatible type "str";
# expected "Union[int, float]"

titles = ["hello", 1]
# ERROR: List item 1 has incompatible type "int"; expected "str"

items: List = ["hello", 1]
# Everything is good!

Note: there are similar annotations for sets: typing.Set and typing.FrozenSet.

Tuples

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
price_container: Tuple[int] = (1,)
price_container: ("hello")
# ERROR: Incompatible types in assignment (expression has type "str",
# variable has type "Tuple[int]")

price_container = (1, 2)
# ERROR: Incompatible types in assignment (expression has type "Tuple[int, int]",
# variable has type "Tuple[int]")

price_with_title: Tuple[int, str] = (1, "hello")
# Everything is good!

# you can use ellipsis to define an "unknown number of elements"
prices: Tuple[int, ...] = (1, 2)
prices: (1,)
prices: (1, "str")
# ERROR: Incompatible types in assignment (expression has type
# "Tuple[int, str]", variable has type "Tuple[int]")

# no type specification equals to Tuple[Any, ...]
something: Tuple = (1, 2, "hello")
# Everything is good!

Dictionaries

1
2
3
4
5
6
7
8
9
# key and type are specified separate
book_authors: Dict[str, str] = {"Fahrenheit 451": "Bradbury"}
book_authors["1984"] = 0
# ERROR: Incompatible types in assignment
# (expression has type "int", target has type "str")

book_authors[1984] = "Orwell"
# ERROR: Invalid index type "int" for "Dict[str, str]";
# expected type "str"

There is also typing.DefaultDict and typing.OrderedDict

Function execution results

  • Function returns nothing:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def nothing(a: int) -> None:
    if a == 1:
    return
    elif a == 2:
    return
    elif a == 3:
    # No return value expected
    return ""
    else:
    pass
  • Function never returns control (e.g. sys.exit)

    1
    2
    3
    def forever() -> NoReturn:
    while True:
    pass
  • Generator function (its body contains an operator yield)

    1
    2
    3
    4
    5
    def generate_two() -> Iterable[int]:
    yield 1
    yield "2"
    # ERROR: Incompatible types in "yield"
    # (actual type "str", expected type "int")