Python Type Hints: Um Guia Prático para Anotações de Tipo
Updated on
A tipagem dinâmica do Python é flexível, mas cria problemas reais em escala. Funções aceitam tipos errados silenciosamente e produzem erros confusos longe do bug real. Refatoração quebra código em lugares que você não consegue prever. Ler o código de outra pessoa significa adivinhar quais tipos fluem através de cada variável, cada parâmetro de função, cada valor de retorno. À medida que as bases de código crescem, esses palpites se tornam bugs, e esses bugs se tornam horas de debugging.
O custo se acumula em ambientes de equipe. Sem informações de tipo, cada chamada de função requer ler a implementação para entender o que ela espera e o que retorna. Revisões de código ficam mais lentas. Novos membros da equipe demoram mais para se integrar. Ferramentas automatizadas não podem ajudar porque não têm informações de tipo para trabalhar.
As type hints do Python resolvem isso adicionando anotações de tipo opcionais que IDEs, verificadores de tipo e humanos podem verificar. Elas documentam sua intenção diretamente no código, capturam categorias inteiras de bugs antes do runtime e desbloqueiam recursos poderosos de edição como autocomplete e detecção de erros inline. Este guia cobre tudo, desde anotações básicas até padrões avançados usados em bases de código Python em produção.
O Que São Type Hints?
Type hints são anotações opcionais que especificam os tipos esperados de variáveis, parâmetros de função e valores de retorno. Elas foram introduzidas na PEP 484 (opens in a new tab) (Python 3.5) e foram refinadas através de PEPs subsequentes incluindo PEP 526 (anotações de variáveis), PEP 604 (sintaxe de union) e PEP 612 (especificações de parâmetros).
A palavra-chave é opcional. Type hints não afetam o comportamento em runtime. Python não as impõe durante a execução. Elas existem para três audiências: desenvolvedores lendo o código, IDEs fornecendo autocomplete e detecção de erros, e verificadores estáticos de tipo como mypy que analisam código sem executá-lo.
# Sem type hints - o que essa função espera?
def process_data(data, threshold, output):
...
# Com type hints - instantaneamente claro
def process_data(data: list[float], threshold: float, output: str) -> dict[str, float]:
...A segunda versão te diz tudo à primeira vista: data é uma lista de floats, threshold é um float, output é uma string, e a função retorna um dicionário mapeando strings para floats. Não há necessidade de ler a implementação ou rastrear sites de chamada.
Anotações de Tipo Básicas
Anotações de Variáveis
Anotações de variáveis usam a sintaxe de dois-pontos introduzida na PEP 526 (Python 3.6):
# Anotações de variáveis básicas
name: str = "Alice"
age: int = 30
height: float = 5.9
is_active: bool = True
raw_data: bytes = b"hello"
# Anotações sem atribuição (apenas declaração)
username: str
count: intVocê pode anotar variáveis sem atribuir um valor. Isso é útil em corpos de classe e blocos condicionais onde a variável recebe atribuição posteriormente.
Parâmetros de Função e Tipos de Retorno
Anotações de função usam dois-pontos para parâmetros e -> para tipos de retorno:
def greet(name: str) -> str:
return f"Hello, {name}!"
def calculate_average(numbers: list[float]) -> float:
return sum(numbers) / len(numbers)
def save_record(record: dict[str, str], overwrite: bool = False) -> None:
"""Funções que não retornam nada usam -> None."""
...A anotação -> None comunica explicitamente que uma função executa uma ação sem retornar um valor significativo. Isso é importante porque distingue retornos None intencionais de instruções return esquecidas.
Tipos Embutidos
Os tipos embutidos do Python mapeiam diretamente para type hints:
| Tipo | Exemplo | Descrição |
|---|---|---|
int | count: int = 10 | Inteiros |
float | price: float = 9.99 | Números de ponto flutuante |
str | name: str = "Bob" | Strings de texto |
bool | active: bool = True | Valores booleanos |
bytes | data: bytes = b"\x00" | Sequências de bytes |
None | result: None = None | O singleton None |
def parse_config(path: str, encoding: str = "utf-8") -> dict[str, str]:
config: dict[str, str] = {}
with open(path, encoding=encoding) as f:
for line in f:
key, _, value = line.partition("=")
config[key.strip()] = value.strip()
return configTipos de Coleção
Sintaxe Moderna (Python 3.9+)
A partir do Python 3.9, você pode usar tipos de coleção embutidos diretamente como tipos genéricos:
# Lists
scores: list[int] = [95, 87, 92]
names: list[str] = ["Alice", "Bob"]
# Dictionaries
user_ages: dict[str, int] = {"Alice": 30, "Bob": 25}
config: dict[str, list[str]] = {"servers": ["a.com", "b.com"]}
# Tuples - comprimento fixo com tipos específicos
point: tuple[float, float] = (3.14, 2.72)
record: tuple[str, int, bool] = ("Alice", 30, True)
# Tuplas de comprimento variável (todo mesmo tipo)
values: tuple[int, ...] = (1, 2, 3, 4, 5)
# Sets e frozensets
tags: set[str] = {"python", "typing"}
constants: frozenset[int] = frozenset({1, 2, 3})Sintaxe Legada (Python 3.5-3.8)
Antes do Python 3.9, você precisava importar tipos genéricos do módulo typing:
from typing import List, Dict, Tuple, Set, FrozenSet
scores: List[int] = [95, 87, 92]
user_ages: Dict[str, int] = {"Alice": 30}
point: Tuple[float, float] = (3.14, 2.72)
tags: Set[str] = {"python", "typing"}Tabela de Comparação de Sintaxe
| Tipo | Python 3.9+ | Python 3.5-3.8 |
|---|---|---|
| List | list[int] | typing.List[int] |
| Dictionary | dict[str, int] | typing.Dict[str, int] |
| Tuple (fixo) | tuple[str, int] | typing.Tuple[str, int] |
| Tuple (variável) | tuple[int, ...] | typing.Tuple[int, ...] |
| Set | set[str] | typing.Set[str] |
| FrozenSet | frozenset[int] | typing.FrozenSet[int] |
| Type | type[MyClass] | typing.Type[MyClass] |
Use a sintaxe moderna sempre que seu projeto tiver como alvo Python 3.9 ou superior. É mais limpa e não requer imports.
Coleções Aninhadas
Tipos de coleção compõem naturalmente para estruturas de dados complexas:
# Matriz: lista de listas de floats
matrix: list[list[float]] = [[1.0, 2.0], [3.0, 4.0]]
# Mapeando usuários para suas listas de scores
gradebook: dict[str, list[int]] = {
"Alice": [95, 87, 92],
"Bob": [78, 85, 90],
}
# Configuração: dicionários aninhados
app_config: dict[str, dict[str, str | int]] = {
"database": {"host": "localhost", "port": 5432},
"cache": {"host": "redis.local", "port": 6379},
}Tipos Optional e Union
Tipos Optional
Um valor que poderia ser None é anotado com Optional ou a sintaxe de union:
from typing import Optional
# Sintaxe pré-3.10
def find_user(user_id: int) -> Optional[str]:
"""Retorna username ou None se não encontrado."""
users = {1: "Alice", 2: "Bob"}
return users.get(user_id)
# Sintaxe Python 3.10+ (preferida)
def find_user(user_id: int) -> str | None:
"""Retorna username ou None se não encontrado."""
users = {1: "Alice", 2: "Bob"}
return users.get(user_id)Optional[str] é exatamente equivalente a str | None. A sintaxe de pipe é mais legível e não requer import.
Tipos Union
Quando um valor pode ser um de vários tipos:
from typing import Union
# Sintaxe pré-3.10
def format_value(value: Union[int, float, str]) -> str:
return str(value)
# Sintaxe Python 3.10+ (preferida)
def format_value(value: int | float | str) -> str:
return str(value)
# Padrão comum: aceitando múltiplos formatos de entrada
def load_data(source: str | Path) -> list[dict[str, str]]:
path = Path(source) if isinstance(source, str) else source
with open(path) as f:
return json.load(f)Comparação de Sintaxe Union
| Padrão | Pré-3.10 | Python 3.10+ |
|---|---|---|
| Nullable | Optional[str] | str | None |
| Dois tipos | Union[int, str] | int | str |
| Múltiplos | Union[int, float, str] | int | float | str |
| Union nullable | Optional[Union[int, str]] | int | str | None |
Tipos Avançados do Módulo typing
Any
Any desabilita a verificação de tipo para um valor específico. Use com moderação como escape:
from typing import Any
def log_event(event: str, payload: Any) -> None:
"""Aceita qualquer tipo de payload - útil para logging genérico."""
print(f"[{event}] {payload}")
# Any é compatível com todo tipo
log_event("click", {"x": 100, "y": 200})
log_event("error", 404)
log_event("message", "hello")TypeVar e Generic
TypeVar cria variáveis de tipo genérico para funções e classes que trabalham com múltiplos tipos enquanto preserva relações de tipo:
from typing import TypeVar
T = TypeVar("T")
def first_element(items: list[T]) -> T:
"""Retorna o primeiro elemento, preservando seu tipo."""
return items[0]
# Verificadores de tipo inferem o tipo de retorno correto
name = first_element(["Alice", "Bob"]) # type: str
score = first_element([95, 87, 92]) # type: int
# TypeVar bounded - restrito a tipos específicos
Numeric = TypeVar("Numeric", int, float)
def add(a: Numeric, b: Numeric) -> Numeric:
return a + bCriando classes genéricas:
from typing import TypeVar, Generic
T = TypeVar("T")
class Stack(Generic[T]):
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
def peek(self) -> T:
return self._items[-1]
def is_empty(self) -> bool:
return len(self._items) == 0
# Uso com tipos específicos
int_stack: Stack[int] = Stack()
int_stack.push(42)
value: int = int_stack.pop()
str_stack: Stack[str] = Stack()
str_stack.push("hello")Callable
Callable anota parâmetros de função e callbacks:
from typing import Callable
# Função que recebe um callback
def apply_operation(
values: list[float],
operation: Callable[[float], float]
) -> list[float]:
return [operation(v) for v in values]
# Uso
import math
results = apply_operation([1.0, 4.0, 9.0], math.sqrt)
# results: [1.0, 2.0, 3.0]
# Assinaturas de callable mais complexas
Comparator = Callable[[str, str], int]
EventHandler = Callable[[str, dict[str, str]], None]
def sort_with_comparator(
items: list[str],
compare: Comparator
) -> list[str]:
import functools
return sorted(items, key=functools.cmp_to_key(compare))Literal
Literal restringe valores a constantes específicas:
from typing import Literal
def set_log_level(level: Literal["DEBUG", "INFO", "WARNING", "ERROR"]) -> None:
print(f"Log level set to {level}")
set_log_level("INFO") # OK
set_log_level("VERBOSE") # Erro de tipo: não é um literal válido
# Útil para parâmetros de modo
def read_file(
path: str,
mode: Literal["text", "binary"] = "text"
) -> str | bytes:
if mode == "text":
return open(path).read()
return open(path, "rb").read()TypedDict
TypedDict define a forma de dicionários com chaves específicas e tipos de valor:
from typing import TypedDict, NotRequired
class UserProfile(TypedDict):
name: str
email: str
age: int
bio: NotRequired[str] # Chave opcional (Python 3.11+)
def display_user(user: UserProfile) -> str:
return f"{user['name']} ({user['email']}), age {user['age']}"
# Verificador de tipo valida a estrutura
user: UserProfile = {
"name": "Alice",
"email": "alice@example.com",
"age": 30,
}
display_user(user) # OKProtocol (Subtipagem Estrutural)
Protocol define interfaces baseadas em estrutura ao invés de herança. Se um objeto tem os métodos necessários, ele satisfaz o protocolo:
from typing import Protocol, runtime_checkable
@runtime_checkable
class Drawable(Protocol):
def draw(self, x: int, y: int) -> None: ...
class Circle:
def draw(self, x: int, y: int) -> None:
print(f"Drawing circle at ({x}, {y})")
class Square:
def draw(self, x: int, y: int) -> None:
print(f"Drawing square at ({x}, {y})")
def render(shape: Drawable, x: int, y: int) -> None:
shape.draw(x, y)
# Ambos funcionam sem herdar de Drawable
render(Circle(), 10, 20)
render(Square(), 30, 40)
# runtime_checkable habilita checagens isinstance
print(isinstance(Circle(), Drawable)) # TrueTypeAlias
TypeAlias cria aliases de tipo explícitos para tipos complexos:
from typing import TypeAlias
# Aliases simples
UserId: TypeAlias = int
JsonDict: TypeAlias = dict[str, "JsonValue"]
JsonValue: TypeAlias = str | int | float | bool | None | list["JsonValue"] | JsonDict
# Aliases complexos simplificam assinaturas
Matrix: TypeAlias = list[list[float]]
Callback: TypeAlias = Callable[[str, int], bool]
Config: TypeAlias = dict[str, str | int | list[str]]
def transform_matrix(m: Matrix, factor: float) -> Matrix:
return [[cell * factor for cell in row] for row in m]
# Python 3.12+ usa a instrução type
# type Vector = list[float]Verificação de Tipos com mypy
Instalando e Executando o mypy
mypy é o verificador estático de tipo mais amplamente usado para Python:
# Instale o mypy
# pip install mypy
# Verifique um único arquivo
# mypy script.py
# Verifique um projeto inteiro
# mypy src/
# Verifique com versão específica do Python
# mypy --python-version 3.10 src/Configuração
Configure mypy em pyproject.toml ou mypy.ini para configurações de todo o projeto:
# Configuração pyproject.toml
# [tool.mypy]
# python_version = "3.10"
# warn_return_any = true
# warn_unused_configs = true
# disallow_untyped_defs = true
# check_untyped_defs = true
# no_implicit_optional = true
# strict_equality = true
# Substituições por módulo
# [[tool.mypy.overrides]]
# module = "third_party_lib.*"
# ignore_missing_imports = trueFlags Comuns do mypy
| Flag | Efeito |
|---|---|
--strict | Habilita todas as verificações estritas (recomendado para novos projetos) |
--ignore-missing-imports | Pula erros para bibliotecas de terceiros sem tipos |
--disallow-untyped-defs | Requer anotações de tipo em todas as funções |
--no-implicit-optional | Não trata default None como Optional |
--warn-return-any | Alerta quando retorna Any de funções tipadas |
--show-error-codes | Mostra códigos de erro para cada erro (útil para supressão) |
Corrigindo Erros Comuns do mypy
# Erro: Tipo de valor de retorno incompatível (recebeu "Optional[str]", esperava "str")
# Correção: Lide com o caso None
def get_name(user_id: int) -> str:
result = lookup(user_id) # retorna str | None
if result is None:
raise ValueError(f"User {user_id} not found")
return result # mypy sabe que result é str aqui
# Erro: Item "None" de "Optional[str]" não tem atributo "upper"
# Correção: Restrinja o tipo primeiro
def format_name(name: str | None) -> str:
if name is not None:
return name.upper()
return "UNKNOWN"
# Erro: Necessária anotação de tipo para variável
# Correção: Adicione anotação explícita
items: list[str] = [] # Não apenas: items = []
# Erro: Tipos incompatíveis em atribuição
# Correção: Use Union ou corrija o tipo
value: int | str = 42
value = "hello" # OK com tipo union
# Suprimir erros específicos quando necessário
x = some_untyped_function() # type: ignore[no-untyped-call]Type Hints na Prática
FastAPI e Pydantic
FastAPI usa type hints para conduzir validação de requisições, serialização e documentação:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class UserCreate(BaseModel):
name: str
email: str
age: int
tags: list[str] = []
class UserResponse(BaseModel):
id: int
name: str
email: str
@app.post("/users", response_model=UserResponse)
async def create_user(user: UserCreate) -> UserResponse:
# FastAPI valida o corpo da requisição contra UserCreate
# e serializa a resposta para corresponder a UserResponse
return UserResponse(id=1, name=user.name, email=user.email)Pydantic usa type hints para automaticamente validar dados, converter tipos e gerar schemas JSON. As anotações de tipo não são apenas documentação -- elas conduzem comportamento em runtime.
Data Science: Tipando DataFrames
Type hints são cada vez mais importantes em fluxos de trabalho de data science:
import pandas as pd
from typing import Any
# Typing básico de DataFrame
def clean_dataframe(df: pd.DataFrame) -> pd.DataFrame:
return df.dropna().reset_index(drop=True)
# Usando pandera para validação de schema
# pip install pandera
import pandera as pa
from pandera.typing import DataFrame, Series
class SalesSchema(pa.DataFrameModel):
product: Series[str]
quantity: Series[int] = pa.Field(ge=0)
price: Series[float] = pa.Field(gt=0)
date: Series[pd.Timestamp]
@pa.check_types
def process_sales(df: DataFrame[SalesSchema]) -> DataFrame[SalesSchema]:
"""Processamento de DataFrame com verificação de tipos."""
return df[df["quantity"] > 0]Benefícios de IDE
Type hints desbloqueiam recursos poderosos de IDE em todos os editores principais:
| Recurso | Sem Type Hints | Com Type Hints |
|---|---|---|
| Autocompletar | Sugestões genéricas | Completions conscientes de contexto para o tipo exato |
| Detecção de Erros | Apenas erros em runtime | Erros inline antes da execução |
| Refatoração | Busca e substituição manual | Renomeação automatizada segura e refatoração |
| Documentação | Requer docstrings ou ler a fonte | Hover mostra tipos inline |
| Navegação | Busca de texto | Pular para definições tipadas e implementações |
Benefícios de Type Hints Entre Ferramentas
| Ferramenta | Como Usa as Type Hints |
|---|---|
| mypy | Verificação estática de tipo, captura bugs antes do runtime |
| Pyright/Pylance | Verificação de tipo e autocomplete do VS Code |
| FastAPI | Validação de requisição/resposta e documentação de API |
| Pydantic | Validação de dados, serialização, gerenciamento de configurações |
| SQLAlchemy 2.0 | Colunas mapeadas, tipos de resultado de query |
| pytest | Inferência de tipo de plugin, typing de fixtures |
| attrs/dataclasses | Geração automática de __init__, __repr__, __eq__ |
Folha de Consulta Rápida (Cheat Sheet) de Type Hints
Aqui está uma tabela de referência rápida das anotações de tipo mais comuns:
| Anotação | Significado | Exemplo |
|---|---|---|
int | Inteiro | count: int = 0 |
float | Float | price: float = 9.99 |
str | String | name: str = "Alice" |
bool | Booleano | active: bool = True |
bytes | Bytes | data: bytes = b"" |
None | Tipo None | -> None |
list[int] | Lista de ints | scores: list[int] = [] |
dict[str, int] | Dict mapeando str para int | ages: dict[str, int] = {} |
tuple[str, int] | Tupla de comprimento fixo | pair: tuple[str, int] |
tuple[int, ...] | Tupla de comprimento variável | nums: tuple[int, ...] |
set[str] | Set de strings | tags: set[str] = set() |
str | None | String nullable (3.10+) | name: str | None = None |
Optional[str] | String nullable (pré-3.10) | name: Optional[str] = None |
int | str | Int ou string (3.10+) | value: int | str |
Union[int, str] | Int ou string (pré-3.10) | value: Union[int, str] |
Any | Qualquer tipo (escape) | data: Any |
Callable[[int], str] | Tipo de função | fn: Callable[[int], str] |
Literal["a", "b"] | Apenas valores específicos | mode: Literal["r", "w"] |
TypeVar("T") | Variável de tipo genérico | T = TypeVar("T") |
ClassVar[int] | Variável de nível de classe | count: ClassVar[int] = 0 |
Final[str] | Não pode ser reatribuído | NAME: Final = "app" |
TypeAlias | Alias de tipo explícito | UserId: TypeAlias = int |
Erros Comuns
| Erro | Problema | Correção |
|---|---|---|
def f(x: list) | Tipo de elemento faltando | def f(x: list[int]) |
items = [] | Tipo não pode ser inferido | items: list[str] = [] |
def f(x: int = None) | Default é None mas tipo é int | def f(x: int | None = None) |
from typing import List (3.9+) | Import desnecessário | Use list[int] diretamente |
def f(x: dict) | Tipos de chave/valor faltando | def f(x: dict[str, int]) |
isinstance(x, list[int]) | Não pode usar genéricos com isinstance | isinstance(x, list) |
def f() -> True | Usando um valor, não um tipo | def f() -> bool |
Anotar self | Redundante, mypy infere | Omita anotação de self |
x: str = 42 | Anotação errada | Combine anotação com tipo real |
Usar demais Any | Derrota o propósito da tipagem | Use tipos específicos ou TypeVar |
# Erro: default mutável com type hint
def bad_append(item: str, items: list[str] = []) -> list[str]:
items.append(item) # Default mutável compartilhado!
return items
# Correção: use None como default
def good_append(item: str, items: list[str] | None = None) -> list[str]:
if items is None:
items = []
items.append(item)
return itemsExemplo Prático: Um Pipeline de Dados Tipado
Aqui está um exemplo completo mostrando como type hints trabalham juntas em um cenário real de processamento de dados. Este padrão é comum em fluxos de trabalho de data science e analytics:
from typing import TypedDict, Callable, TypeAlias
from pathlib import Path
import csv
# Defina formas de dados com TypedDict
class RawRecord(TypedDict):
name: str
value: str
category: str
class ProcessedRecord(TypedDict):
name: str
value: float
category: str
normalized: float
# Type alias para funções de transformação
Transform: TypeAlias = Callable[[list[ProcessedRecord]], list[ProcessedRecord]]
def load_csv(path: Path) -> list[RawRecord]:
"""Carrega dados CSV com saída tipada."""
records: list[RawRecord] = []
with open(path) as f:
reader = csv.DictReader(f)
for row in reader:
records.append(RawRecord(
name=row["name"],
value=row["value"],
category=row["category"],
))
return records
def parse_records(raw: list[RawRecord]) -> list[ProcessedRecord]:
"""Converte registros raw de strings para registros tipados."""
max_value = max(float(r["value"]) for r in raw) or 1.0
return [
ProcessedRecord(
name=r["name"],
value=float(r["value"]),
category=r["category"],
normalized=float(r["value"]) / max_value,
)
for r in raw
]
def filter_by_category(
records: list[ProcessedRecord],
category: str
) -> list[ProcessedRecord]:
"""Filtra registros, entrada e saída totalmente tipadas."""
return [r for r in records if r["category"] == category]
def apply_transforms(
records: list[ProcessedRecord],
transforms: list[Transform]
) -> list[ProcessedRecord]:
"""Aplica uma cadeia de funções de transformação tipadas."""
result = records
for transform in transforms:
result = transform(result)
return result
# Uso
raw = load_csv(Path("data.csv"))
processed = parse_records(raw)
filtered = filter_by_category(processed, "electronics")Cada função neste pipeline tem tipos de entrada e saída claros. Um verificador de tipo pode verificar se a saída de uma função corresponde à entrada da próxima. Se alguém mudar a estrutura de ProcessedRecord, o mypy sinaliza todos os lugares que precisam de atualização.
Visualizando Dados Tipados com PyGWalker
Ao trabalhar com DataFrames tipados em fluxos de trabalho de data science, PyGWalker (opens in a new tab) transforma seus DataFrames pandas em uma interface de visualização interativa, semelhante ao Tableau. Ele funciona bem junto com pipelines verificadas por tipo porque os dados estruturados que você produz com tipagem adequada alimentam diretamente gráficos e dashboards exploráveis:
import pandas as pd
import pygwalker as pyg
# Seu pipeline tipado produz dados limpos e estruturados
data: list[ProcessedRecord] = parse_records(raw)
df = pd.DataFrame(data)
# PyGWalker renderiza como uma visualização interativa
walker = pyg.walk(df)Para ambientes de notebook interativos, RunCell (opens in a new tab) fornece uma experiência Jupyter powered by AI onde código verificado por tipo e exploração visual de dados trabalham juntos perfeitamente.
Perguntas Frequentes
Type hints afetam a performance do Python?
Não. O runtime do Python ignora type hints completamente. Elas são armazenadas como metadados em funções e variáveis, mas nunca avaliadas durante a execução normal. Há uma sobrecarga de memória negligenciável para armazenar as anotações, mas nenhum impacto na velocidade de execução. Frameworks como Pydantic e FastAPI leem anotações na inicialização para construir lógica de validação, mas isso é comportamento do framework, não uma característica da linguagem Python.
Type hints são obrigatórias em Python?
Não. Type hints são completamente opcionais. Python permanece uma linguagem dinamicamente tipada, e código roda identicamente com ou sem anotações. No entanto, type hints são fortemente recomendadas para qualquer projeto com mais de um desenvolvedor ou qualquer base de código que precise de manutenção de longo prazo. Projetos Python importantes como FastAPI, SQLAlchemy e Django dependem cada vez mais de type hints.
Qual é a diferença entre type hints e verificação de tipo?
Type hints são as anotações que você escreve no seu código, como x: int ou -> str. Verificação de tipo é o processo de verificar se seu código é consistente com essas anotações. Verificação de tipo é realizada por ferramentas externas como mypy, Pyright ou Pylance -- não pelo próprio Python. Você pode ter type hints sem rodar um verificador de tipo, e as hints ainda fornecem valor através de autocomplete de IDE e documentação.
Devo usar typing.List ou list para type hints?
Use list[int] em minúsculas se seu projeto tem como alvo Python 3.9 ou posterior. typing.List[int] em maiúsculas é a sintaxe legada requerida para Python 3.5-3.8. A sintaxe em minúsculas é mais limpa, não requer import e é a abordagem recomendada daqui para frente. O mesmo se aplica a dict vs typing.Dict, tuple vs typing.Tuple e set vs typing.Set.
Qual é o melhor verificador de tipo para Python?
mypy é o verificador de tipo mais estabelecido e amplamente usado para Python. Pyright (usado pela extensão Pylance do VS Code) é mais rápido e captura alguns erros que o mypy perde. Ambos são ativamente mantidos. Para a maioria dos projetos, use aquele que se integra melhor com seu editor. mypy é o padrão para pipelines de CI. Pyright fornece a melhor experiência em tempo real no VS Code. Você pode rodar ambos em um projeto sem conflitos.
Conclusão
As type hints do Python fazem a ponte entre a flexibilidade dinâmica do Python e as garantias de segurança de linguagens estaticamente tipadas. Elas capturam bugs antes do runtime, tornam o código auto-documentado e desbloqueiam recursos poderosos de IDE que aceleram o desenvolvimento.
Comece com o básico: anote parâmetros de função, tipos de retorno e variáveis complexas. Use list[int], dict[str, str] e str | None para tipos do dia a dia. Rode mypy em seu pipeline de CI para capturar erros de tipo automaticamente. À medida que sua confiança cresce, adote padrões avançados como TypedDict, Protocol e Generic para modelar tipos de domínio complexos.
O investimento vale a pena rapidamente. Uma única anotação de tipo que previne um bug de produção justifica horas de esforço de digitação. Equipes reportam integração mais rápida, refatoração mais segura e menos surpresas em runtime após adotar type hints. Com frameworks como FastAPI e Pydantic construindo todo seu design em torno de anotações de tipo, Python tipado não é uma prática de nicho -- é a direção em que o ecossistema está se movendo.