Skip to content

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

    intermediario/cap37_type_hints.pylinhas 10 a 16
    def saudar(nome: str, vezes: int = 1) -> str:
        return (f"Olá, {nome}! " * vezes).strip()
    
    
    print(saudar("Ana", 2))
    print(saudar.__annotations__)
    print(saudar(123))
    
    Output
    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:

    intermediario/cap37_type_hints.pylinhas 21 a 29
    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"))
    
    Output
    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:

    intermediario/cap37_type_hints.pylinhas 34 a 56
    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)
    
    Output
    [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:

    Terminal
    uv add --dev mypy
    uv run mypy exemplos/tipos/erro_de_tipo.py
    

    The file to check calls a function with the wrong type:

    exemplos/tipos/erro_de_tipo.py
    def dobrar(valor: int) -> int:
        return valor * 2
    
    
    dobrar("3")
    
    Output
    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") or from __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.