Chapter 37, Intermediate
Type hints
Type annotations document the contract of the code and allow tools to find errors before running. Python itself ignores the annotations.
Code for this chapter: intermediario/cap37_type_hints.py
Annotating parameters and return
The annotation goes after a colon in the parameters and after -> in the return. They are stored in __annotations__, but the interpreter does not check them.:
def saudar(nome: str, vezes: int = 1) -> str:
return (f"Olá, {nome}! " * vezes).strip()
print(saudar("Ana", 2))
print(saudar.__annotations__)
print(saudar(123))
Olá, Ana! Olá, Ana!
{'nome': <class 'str'>, 'vezes': <class 'int'>, 'return': <class 'str'>}
Olá, 123!
The last call passes an integer where text was expected, and Python runs without complaint. The error is reported by an external tool, the type checker.
Collections, optionals, and unions
Since Python 3.9 you can use list[int] and dict[str, int] directly. The vertical bar (int | None, since 3.10) expresses a union:
def media(valores: list[float]) -> float:
return sum(valores) / len(valores)
def buscar(config: dict[str, int], chave: str) -> int | None:
return config.get(chave)
print(media([1.0, 2.0, 3.0]), buscar({"porta": 80}, "host"))
2.0 None
Aliases, functions, and structures
To name a complex type, Python 3.12 introduced the statement type. Functions as arguments use Callable. And for records with a known shape there is NamedTuple and TypedDict:
from collections.abc import Callable
from typing import NamedTuple, TypedDict
type Predicado = Callable[[int], bool]
def filtrar(numeros: list[int], teste: Predicado) -> list[int]:
return [n for n in numeros if teste(n)]
class Ponto(NamedTuple):
x: int
y: int
class Usuario(TypedDict):
nome: str
idade: int
u: Usuario = {"nome": "Ana", "idade": 30}
print(filtrar([1, 2, 3, 4], lambda n: n % 2 == 0))
print(Ponto(1, 2), u)
[2, 4]
Ponto(x=1, y=2) {'nome': 'Ana', 'idade': 30}
The "truth" constant for type tools is typing.Final: LIMITE: Final = 3 warns that reassigning is an error.
Who checks
The most used checker is mypy. Pyright is another good option. Both read the code without executing it and point out inconsistencies. Install as a development tool for the project:
uv add --dev mypy
uv run mypy exemplos/tipos/erro_de_tipo.py
The file to check calls a function with the wrong type:
def dobrar(valor: int) -> int:
return valor * 2
dobrar("3")
exemplos/tipos/erro_de_tipo.py:5: error: Argument 1 to "dobrar" has incompatible type "str"; expected "int" [arg-type]
Found 1 error in 1 file (checked 1 source file)
References to types not yet defined
Before Python 3.14, annotating with a class defined further down required quotes (
"Cliente") orfrom __future__ import annotations. Starting from 3.14, annotations are evaluated lazily by default, and this problem disappears. If your project still supports earlier versions, keep the__future__.
Exercise 1
Annotate a grouping function
Write agrupar_por_inicial(palavras: list[str]) -> dict[str, list[str]], which groups the words by the first lowercase letter.