Type Hints em Python
Aprenda type hints em Python: como anotar variáveis, funções e classes, usar o módulo typing e verificar tipos com mypy.
Type hints permitem que você associe informações de tipo esperadas a variáveis, parâmetros de funções e valores de retorno. Python não os aplica em tempo de execução — eles são metadados consumidos por editores, linters e verificadores de tipo como o mypy para capturar bugs antes de você executar uma única linha.
Este capítulo aborda:
- Por que type hints são importantes e quando usá-los
- Anotando variáveis e funções
- Tipos embutidos e o módulo
typing(List,Dict,Optional,Union,Tuple,Any,Callable) - Sintaxe moderna (Python 3.10+)
- Anotando classes e
self - Genéricos e aliases de tipo
- Análise estática com mypy
- Armadilhas comuns
Por que usar Type Hints?
Python é tipado dinamicamente: uma variável pode conter qualquer valor de qualquer tipo. Essa flexibilidade é poderosa, mas torna bases de código grandes mais difíceis de navegar — você não consegue saber o tipo do argumento de uma função apenas lendo o local de chamada.
Type hints resolvem isso sem abrir mão do dinamismo do Python:
- Editores exibem erros imediatamente. VS Code, PyCharm e outros destacam incompatibilidades de tipo enquanto você digita.
- Refatoração fica mais segura. Mude a assinatura de uma função e o verificador de tipo indica cada local de chamada que quebra.
- O código se autodocumenta.
def greet(name: str) -> strcomunica o contrato sem precisar de uma docstring. - Bibliotecas ficam mais fáceis de usar. Bibliotecas tipadas expõem autocomplete para cada atributo e método.
Type hints foram introduzidos no Python 3.5 por meio da PEP 484. A sintaxe foi refinada em cada versão principal desde então. Os exemplos abaixo indicam a versão mínima do Python onde a sintaxe ficou disponível pela primeira vez.
Anotando Variáveis
Adicione dois-pontos após o nome da variável seguido do tipo:
name: str = "Alice"
age: int = 30
price: float = 9.99
is_active: bool = TrueVocê também pode declarar o tipo de uma variável sem atribuir um valor ainda. Isso é chamado de declaração antecipada e é útil dentro de classes ou no nível do módulo:
user_id: int # declared but not yet assigned
user_id = 42Anotações de tipo em variáveis no nível do módulo não afetam o comportamento em tempo de execução — elas são armazenadas no dicionário __annotations__ do módulo, mas caso contrário são ignoradas pelo interpretador.
Anotando Funções
Coloque anotações nos parâmetros (após os dois-pontos) e no valor de retorno (após -> antes dos dois-pontos que encerram a assinatura):
def add(a: int, b: int) -> int:
return a + b
def greet(name: str) -> str:
return f"Hello, {name}!"
def send_email(to: str, subject: str, body: str) -> None:
print(f"Sending '{subject}' to {to}")
result: int = add(3, 5)
message: str = greet("Alice")-> None significa que a função não possui valor de retorno significativo (ela retorna None implicitamente). Omitir a anotação de retorno também é válido, mas o -> None explícito deixa a intenção clara.
Parâmetros com Valor Padrão
Os valores padrão vão após a anotação:
def connect(host: str, port: int = 8080, secure: bool = False) -> None:
print(f"Connecting to {host}:{port} (secure={secure})")
connect("example.com") # uses defaults
connect("example.com", 443, True)*args e **kwargs
Anote o tipo do elemento, não o tipo da coleção:
def total(*prices: float) -> float:
return sum(prices)
def create_user(**fields: str) -> dict:
return fields
print(round(total(9.99, 4.50, 12.00), 2)) # 26.49
print(create_user(name="Bob", role="admin"))*prices: float significa que cada argumento posicional é um float; em tempo de execução, prices ainda é uma tuple regular de floats. Da mesma forma, **fields: str significa que o valor de cada argumento nomeado é uma str.
O Módulo typing
Para qualquer coisa além dos tipos embutidos básicos, importe do módulo typing (Python 3.5+). A partir do Python 3.9, muitos tipos do typing foram incorporados diretamente nos equivalentes embutidos (veja Sintaxe Moderna abaixo).
List, Tuple, Set, Dict
from typing import List, Tuple, Set, Dict
def first_names(users: List[str]) -> str:
return users[0] if users else ""
def dimensions() -> Tuple[int, int, int]:
return (1920, 1080, 32)
def unique_tags(items: List[str]) -> Set[str]:
return set(items)
def word_count(text: str) -> Dict[str, int]:
counts: Dict[str, int] = {}
for word in text.split():
counts[word] = counts.get(word, 0) + 1
return counts
print(first_names(["Alice", "Bob"])) # Alice
print(dimensions()) # (1920, 1080, 32)
print(unique_tags(["py", "web", "py"])) # {'py', 'web'}
print(word_count("one two one")) # {'one': 2, 'two': 1}Optional
Optional[X] é uma abreviação para Union[X, None]. Use-o sempre que um valor pode estar ausente:
from typing import Optional
def find_user(user_id: int) -> Optional[str]:
db = {1: "Alice", 2: "Bob"}
return db.get(user_id) # returns None if not found
name = find_user(1)
if name is not None:
print(name.upper()) # ALICE
missing = find_user(99)
print(missing) # NoneUm verificador de tipo vê Optional[str] e sabe que você deve verificar None antes de chamar métodos de string no resultado. Sem a verificação, ele relata um erro.
Union
Union[X, Y] significa que o valor pode ser do tipo X ou do tipo Y:
from typing import Union
def stringify(value: Union[int, float, str]) -> str:
return str(value)
print(stringify(42)) # 42
print(stringify(3.14)) # 3.14
print(stringify("hi")) # hiUnion é mais útil quando uma função genuinamente aceita múltiplos tipos não relacionados. Se você se pegar escrevendo Union[str, None], use Optional[str] — é mais idiomático.
Callable
Callable[[ArgTypes...], ReturnType] anota uma função passada como argumento:
from typing import Callable
def apply_twice(func: Callable[[int], int], value: int) -> int:
return func(func(value))
def double(n: int) -> int:
return n * 2
print(apply_twice(double, 3)) # 12Callable[[int], int] significa: um callable que recebe um argumento int e retorna um int. Se a lista de argumentos for complexa ou desconhecida, use Callable[..., ReturnType].
Any
Any é um tipo especial que desativa a verificação de tipo para aquele valor. Todo tipo pode ser atribuído a Any e de Any:
from typing import Any
def log(value: Any) -> None:
print(value)
log(42)
log("hello")
log([1, 2, 3])Use Any com moderação — é uma válvula de escape que remove a própria proteção que os type hints fornecem. É apropriado ao interagir com código de terceiros sem tipagem, ou durante a migração gradual de uma base de código grande.
Sintaxe Moderna (Python 3.9+, 3.10+)
Genéricos embutidos (Python 3.9+)
A partir do Python 3.9, você pode usar os tipos embutidos diretamente como genéricos, sem precisar importar de typing:
# Python 3.9+
def word_count(text: str) -> dict[str, int]:
counts: dict[str, int] = {}
for word in text.split():
counts[word] = counts.get(word, 0) + 1
return counts
def first(items: list[int]) -> int | None:
return items[0] if items else None
print(word_count("cat dog cat")) # {'cat': 2, 'dog': 1}
print(first([10, 20, 30])) # 10
print(first([])) # NoneUse list[str] em vez de List[str], dict[str, int] em vez de Dict[str, int], e assim por diante.
Sintaxe de união X | Y (Python 3.10+)
O Python 3.10 introduziu o operador | para uniões, substituindo Union[X, Y] e Optional[X]:
# Python 3.10+
def parse(value: str | int | None) -> str:
if value is None:
return "nothing"
return str(value)
print(parse("hello")) # hello
print(parse(42)) # 42
print(parse(None)) # nothingstr | None é equivalente a Optional[str]. Essa sintaxe é mais limpa e fácil de ler.
Anotando Classes
Anote atributos de instância dentro de __init__ e adicione anotações de retorno aos métodos:
class BankAccount:
owner: str # class-level annotation (no default value)
balance: float
def __init__(self, owner: str, initial_balance: float = 0.0) -> None:
self.owner = owner
self.balance = initial_balance
def deposit(self, amount: float) -> None:
if amount <= 0:
raise ValueError("Deposit amount must be positive.")
self.balance += amount
def withdraw(self, amount: float) -> bool:
if amount > self.balance:
return False
self.balance -= amount
return True
def __repr__(self) -> str:
return f"BankAccount(owner={self.owner!r}, balance={self.balance:.2f})"
account = BankAccount("Alice", 100.0)
account.deposit(50.0)
print(account.withdraw(30.0)) # True
print(account) # BankAccount(owner='Alice', balance=120.00)A anotação em self é sempre inferida — você nunca escreve self: BankAccount. O tipo de retorno de __init__ é sempre None.
ClassVar
Use ClassVar[T] (do módulo typing) para marcar um atributo que pertence à classe, não a cada instância:
from typing import ClassVar
class Config:
MAX_RETRIES: ClassVar[int] = 3
timeout: int
def __init__(self, timeout: int) -> None:
self.timeout = timeout
print(Config.MAX_RETRIES) # 3Um verificador de tipo avisa se você tentar definir um ClassVar em uma instância — ele é destinado a ser compartilhado no nível da classe.
Aliases de Tipo
Um alias de tipo dá a um tipo longo ou complexo um nome mais curto e significativo:
from typing import List, Tuple
# Simple alias
UserID = int
Filename = str
# Structured alias
Coordinates = Tuple[float, float]
Matrix = List[List[float]]
def distance(p1: Coordinates, p2: Coordinates) -> float:
return ((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2) ** 0.5
print(distance((0.0, 0.0), (3.0, 4.0))) # 5.0A partir do Python 3.12, use a instrução type para aliases explícitos e inspecionáveis:
# Python 3.12+
type Vector = list[float]
type Matrix = list[Vector]Genéricos com TypeVar
TypeVar permite escrever uma única função que funciona com qualquer tipo, preservando as relações de tipo:
from typing import TypeVar, List
T = TypeVar("T")
def first_item(items: List[T]) -> T:
return items[0]
x: int = first_item([1, 2, 3]) # x is int
s: str = first_item(["a", "b"]) # s is strO verificador de tipo infere a partir do argumento o que é T e carrega essa informação até o tipo de retorno. Sem TypeVar, você teria que retornar Any e perder a segurança de tipos.
Você pode restringir TypeVar a um conjunto de tipos permitidos:
from typing import TypeVar
Numeric = TypeVar("Numeric", int, float)
def double(n: Numeric) -> Numeric:
return n * 2
print(double(4)) # 8 (int)
print(double(2.5)) # 5.0 (float)Verificação Estática de Tipos com mypy
mypy é o verificador de tipos estático mais amplamente usado para Python. Instale-o com pip:
pip install mypyEm seguida, execute-o em um arquivo:
mypy my_script.pyExemplo: capturando um bug com mypy
Salve o seguinte como demo.py:
def greet(name: str) -> str:
return f"Hello, {name}!"
result = greet(42) # passing int instead of str
print(result.upper())Executar mypy demo.py reporta:
demo.py:4: error: Argument 1 to "greet" has incompatible type "int"; expected "str"
Found 1 error in 1 file (checked 1 source file)O próprio Python executa o código normalmente (f-strings coagem qualquer tipo), mas o mypy detectou a incompatibilidade antes de você descobri-la em produção.
Opções úteis do mypy
| Flag | Efeito |
|---|---|
--strict | Ativa todas as verificações opcionais (recomendado para novos projetos) |
--ignore-missing-imports | Suprime erros sobre stubs de terceiros ausentes |
--check-untyped-defs | Também verifica o tipo de funções sem anotações |
--disallow-untyped-defs | Exige anotações em todas as definições de função |
Um mypy.ini (ou [tool.mypy] em pyproject.toml) mantém a configuração fora da linha de comando:
[mypy]
strict = true
ignore_missing_imports = trueTipagem Gradual
Você não precisa anotar cada função de uma vez. Python suporta tipagem gradual: código anotado e não anotado coexistem pacificamente. mypy pula funções não anotadas por padrão (a menos que --check-untyped-defs esteja ativado).
Uma abordagem prática para uma base de código existente:
- Adicione anotações ao novo código desde o início.
- Anote primeiro as funções mais chamadas ou mais propensas a erros.
- Habilite
--strictmódulo por módulo conforme a cobertura melhorar. - Use
Anyapenas onde uma biblioteca de terceiros não for tipada, e adicione um comentário explicando o motivo.
Armadilhas Comuns
Referências antecipadas
Se um tipo se refere a uma classe definida mais adiante no mesmo arquivo, coloque o nome entre aspas para torná-lo uma string (uma referência antecipada):
class Node:
def __init__(self, value: int, next: "Node | None" = None) -> None:
self.value = value
self.next = next
head = Node(1, Node(2))
print(head.value, head.next.value) # 1 2A partir do Python 3.10+, adicione from __future__ import annotations no topo do arquivo. Isso torna todas as anotações strings preguiçosas e elimina a necessidade de aspas manuais.
Anotações em tempo de execução
Por padrão, as anotações no Python 3.9 e versões anteriores são avaliadas imediatamente. Isso significa que uma referência antecipada sem aspas gera um NameError:
# Works (with quotes):
def clone(self: "MyClass") -> "MyClass": ...Com from __future__ import annotations (Python 3.7+), todas as anotações são armazenadas como strings e avaliadas apenas quando inspecionadas — resolvendo o problema de referências antecipadas automaticamente.
None vs Optional
Um erro comum é anotar um tipo de retorno como str quando a função pode realmente retornar None. Sempre use Optional[str] (ou str | None) quando None é um retorno possível:
from typing import Optional
# Wrong — mypy will flag callers that assume this is always str
def get_name(user_id: int) -> str:
if user_id == 0:
return None # type: ignore — this is the bug
# Correct
def get_name_safe(user_id: int) -> Optional[str]:
if user_id == 0:
return None
return "Alice"list vs List (compatibilidade de versão)
Se seu código roda no Python 3.8 ou anterior, você deve usar from typing import List e escrever List[str]. No Python 3.9+, list[str] funciona diretamente. Se precisar suportar ambos, use as importações do typing ou adicione from __future__ import annotations.
Referência Rápida
| Anotação | Significado |
|---|---|
x: int | Variável x é um inteiro |
def f(a: str) -> bool | Parâmetro a é str; valor de retorno é bool |
-> None | Função não retorna nada significativo |
Optional[str] | str ou None |
Union[int, str] | int ou str |
list[int] / List[int] | Lista de inteiros |
dict[str, int] / Dict[str, int] | Dict mapeando str para int |
tuple[int, str] / Tuple[int, str] | Tuple de (int, str) |
Callable[[int], str] | Função que recebe int e retorna str |
Any | Qualquer tipo (desativa a verificação) |
ClassVar[T] | Atributo no nível da classe |
TypeVar("T") | Variável de tipo genérico |
Tópicos Relacionados
- Python Functions — onde ficam as anotações de tipo nos parâmetros e tipos de retorno.
- Python Classes and Objects — para anotar
__init__, métodos e atributos de classe. - Python Dataclasses — anotações de tipo são obrigatórias para declarar campos de dataclass.
- Python Abstract Classes — classes base abstratas funcionam naturalmente com type hints.