Type Hints (ou dicas de tipo) são uma das funcionalidades mais importantes introduzidas no Python 3.5 através da PEP 484. Eles permitem que você indique os tipos esperados de variáveis, parâmetros e retornos de funções, tornando seu código mais legível, seguro e fácil de manter.
Neste guia completo, você vai aprender tudo sobre Type Hints em Python: desde os conceitos básicos até tópicos avançados como Generics, Protocol e TypeVar, com exemplos práticos que poderá aplicar imediatamente nos seus projetos.
O que são Type Hints?
Type Hints são anotações de tipo que você adiciona ao código Python para indicar que tipo de dado uma variável, parâmetro ou retorno de função deve ter. Eles são opcionais — o Python continua sendo uma linguagem de tipagem dinâmica — mas ferramentas como mypy, Pyright e Pylance usam essas anotações para realizar verificação estática de tipos.
def saudacao(nome: str) -> str:
return f"Olá, {nome}!"
print(saudacao("Maria")) # Funciona
print(saudacao(42)) # mypy acusaria erro
No exemplo acima, nome: str indica que o parâmetro deve ser uma string, e -> str indica que a função retorna uma string. O código roda normalmente, mas ferramentas de verificação apontariam o segundo uso como incorreto.
Fonte oficial: Documentação Python - Módulo typing
Por que usar Type Hints?
Adotar Type Hints traz diversos benefícios:
- Código mais legível: outros desenvolvedores entendem imediatamente que tipo de dados cada função espera e retorna
- Detecção precoce de bugs: ferramentas como mypy encontram erros de tipo antes mesmo de executar o código
- Autocompletar inteligente: IDEs como VS Code, PyCharm e Vim oferecem sugestões mais precisas com base nos tipos
- Documentação viva: os tipos funcionam como documentação auto-atualizável que nunca fica desatualizada
- Refatoração segura: ao mudar a assinatura de uma função, o verificador de tipos aponta todos os locais que precisam ser ajustados
Fonte: Real Python - Type Checking in Python
Type Hints Básicos
Tipos Simples
Os tipos mais básicos são int, float, str e bool:
def calcular_area(raio: float) -> float:
return 3.14159 * raio ** 2
def eh_maior_de_idade(idade: int) -> bool:
return idade >= 18
def obter_nome() -> str:
return "Python"
def processar(logico: bool) -> None:
if logico:
print("Verdadeiro")
Note que None é usado como retorno quando a função não retorna nada.
Tipos de Coleção
Para listas, dicionários, tuplas e conjuntos, use o módulo typing ou, a partir do Python 3.9, os próprios tipos padrão:
from typing import List, Dict, Tuple, Set
Python 3.8 e anteriores
nomes: List[str] = ["Ana", "João", "Maria"]
precos: Dict[str, float] = {"banana": 2.50, "maca": 3.00}
coordenada: Tuple[float, float] = (-23.55, -46.63)
identificadores: Set[int] = {1, 2, 3}
Python 3.9+
nomes: list[str] = ["Ana", "João", "Maria"]
precos: dict[str, float] = {"banana": 2.50, "maca": 3.00}
A partir do Python 3.9, você pode usar list[str] em vez de List[str], simplificando as importações. Essa mudança foi proposta na PEP 585.
Optional e Union
Frequentemente uma variável pode ser de mais de um tipo. Para isso existem Optional e Union:
from typing import Optional, Union
Optional significa que pode ser X ou None
def buscar_usuario(id: int) -> Optional[str]:
usuarios = {1: "Ana", 2: "João"}
return usuarios.get(id) # Pode retornar str ou None
Union significa que pode ser um dos tipos listados
def formatar_valor(valor: Union[int, float, str]) -> str:
if isinstance(valor, str):
return valor
return f"R$ {valor:.2f}"
Python 3.10+ aceita syntaxe com pipe
def processar_id(item_id: int | str) -> bool:
return bool(item_id)
Em Python 3.10, a PEP 604 introduziu a sintaxe com pipe (int | str), tornando o código mais limpo.
Type Aliases
Para tipos complexos que se repetem, crie aliases:
from typing import List, Tuple
Type alias para coordenadas
Coordenada = Tuple[float, float]
Rota = List[Coordenada]
def calcular_distancia(p1: Coordenada, p2: Coordenada) -> float:
from math import sqrt
return sqrt((p2[0] - p1[0])2 + (p2[1] - p1[1])2)
def planejar_rota(pontos: Rota) -> float:
distancia_total = 0.0
for i in range(len(pontos) - 1):
distancia_total += calcular_distancia(pontos[i], pontos[i + 1])
return distancia_total
Aliases tornam o código mais semântico e facilitam mudanças futuras.
Tipagem em Funções
Parâmetros com Valores Padrão
def criar_perfil(nome: str, idade: int = 0, ativo: bool = True) -> dict:
return {"nome": nome, "idade": idade, "ativo": ativo}
*args e **kwargs
from typing import Any
def somar(*args: int) -> int:
return sum(args)
def log(mensagem: str, **kwargs: Any) -> None:
print(f"{mensagem}: {kwargs}")
Callable
Para tipar funções recebidas como parâmetro:
from typing import Callable
def executar_operacao(
a: int, b: int, operacao: Callable[[int, int], int]
) -> int:
return operacao(a, b)
resultado = executar_operacao(10, 5, lambda x, y: x + y)
print(resultado) # 15
Classes e Type Hints
Métodos e Self
class Pessoa:
def __init__(self, nome: str, idade: int) -> None:
self.nome = nome
self.idade = idade
def aniversario(self) -> None:
self.idade += 1
def apresentar(self) -> str:
return f"{self.nome} tem {self.idade} anos"
Atributos de Classe
class Configuracao:
versao: str = "1.0"
timeout: int
debug: bool
def __init__(self, timeout: int, debug: bool = False) -> None:
self.timeout = timeout
self.debug = debug
Tipos Avançados
Literal
Restringe um valor a constantes específicas:
from typing import Literal
def set_modo(modo: Literal["rápido", "lento", "eco"]) -> None:
print(f"Modo {modo} ativado")
set_modo("rápido") # OK
set_modo("turbo") # Erro de tipo!
TypedDict
Define a estrutura de dicionários:
from typing import TypedDict
class Usuario(TypedDict):
nome: str
email: str
idade: int
def criar_usuario(dados: Usuario) -> Usuario:
return dados
user: Usuario = {"nome": "Ana", "email": "[email protected]", "idade": 30}
Final
Indica que um valor não deve ser sobrescrito:
from typing import Final
MAX_TENTATIVAS: Final[int] = 3
PI: Final[float] = 3.14159
Ferramentas de tipo acusariam erro ao tentar modificar
MAX_TENTATIVAS = 5 # Erro!
Generics e TypeVar
Generics permitem criar funções e classes que funcionam com múltiplos tipos de forma segura:
from typing import TypeVar, Generic, List
T = TypeVar("T") # TypeVar genérico
def primeiro_elemento(lista: List[T]) -> T:
return lista[0]
print(primeiro_elemento([1, 2, 3])) # int
print(primeiro_elemento(["a", "b"])) # str
TypeVar com Restrição
from typing import TypeVar
Numero = TypeVar("Numero", int, float)
def somar_valores(a: Numero, b: Numero) -> Numero:
return a + b
somar_valores(1, 2) # OK
somar_valores(1.5, 2.5) # OK
somar_valores("a", "b") # Erro!
Generic em Classes
from typing import Generic, TypeVar, List
T = TypeVar("T")
class Pilha(Generic[T]):
def init(self) -> None:
self._itens: List[T] = []
def push(self, item: T) -> None:
self._itens.append(item)
def pop(self) -> T:
return self._itens.pop()
pilha_int = Pilha[int]()
pilha_int.push(10)
pilha_int.push(20)
print(pilha_int.pop()) # 20
pilha_str = Pilha[str]()
pilha_str.push("Python")
print(pilha_str.pop()) # Python
Protocol (Structural Subtyping)
Protocol, introduzido na PEP 544, permite duck typing estático:
from typing import Protocol
class Falavel(Protocol):
def falar(self) -> str:
...
class Pessoa:
def falar(self) -> str:
return "Olá!"
class Robo:
def falar(self) -> str:
return "Beep boop"
def saudar(entidade: Falavel) -> None:
print(entidade.falar())
saudar(Pessoa()) # Olá!
saudar(Robo()) # Beep boop
Com Protocol, qualquer classe que implemente o método falar é aceita, sem precisar herdar de uma classe base.
Anotações em Variáveis
Você também pode anotar variáveis diretamente:
from typing import List, Optional
nome: str = "Python"
versao: float = 3.12
tags: List[str] = ["dinâmico", "versátil", "moderno"]
endereco: Optional[str] = None
Ferramentas de Verificação de Tipo
As principais ferramentas para verificação estática de tipos em Python são:
- mypy: a ferramenta padrão, criada por Jukka Lehtosalo sob orientação de Guido van Rossum. Suporta a maioria das funcionalidades do módulo typing.
- Pyright: ferramenta da Microsoft usada pelo Pylance (extensão do VS Code). Extremamente rápida e com excelente suporte a tipos.
- pyre: ferramenta do Facebook (Meta) para type checking em Python.
Fonte oficial: Site oficial do mypy
Type Hints com Dataclasses
As dataclasses, introduzidas no Python 3.7, combinam perfeitamente com Type Hints. Consulte também nosso guia completo sobre Data Classes em Python.
from dataclasses import dataclass
from typing import List, Optional
@dataclass
class Produto:
nome: str
preco: float
quantidade: int = 0
categorias: Optional[List[str]] = None
def valor_total(self) -> float:
return self.preco * self.quantidade
produto = Produto("Notebook", 4500.00, 2)
print(f"Total: R$ {produto.valor_total():.2f}")
Documentação oficial: Python Data Classes
Type Hints com FastAPI e Pydantic
FastAPI usa Type Hints intensamente para validação automática de dados e geração de documentação. Se você quer aprender mais sobre APIs, veja nosso guia de FastAPI: Criando APIs RESTful.
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List, Optional
app = FastAPI()
class Item(BaseModel):
nome: str
preco: float
disponivel: bool = True
tags: Optional[List[str]] = None
@app.post("/itens/")
async def criar_item(item: Item) -> Item:
return item
@app.get("/itens/{item_id}")
async def ler_item(item_id: int) -> dict:
return {"item_id": item_id}
Pydantic usa Type Hints para validar e converter dados automaticamente. Veja mais em: Documentação Pydantic
Boas Práticas com Type Hints
1. Seja Gradual
Você não precisa tipar tudo de uma vez. Comece pelas funções públicas e APIs principais, depois expanda gradualmente.
2. Prefira Tipos Específicos
Em vez de Any, use tipos mais específicos sempre que possível. Any desativa a verificação de tipos.
from typing import Any
Evite
def processar(dados: Any) -> Any:
return dados
Prefira
def processar(dados: list[str]) -> list[str]:
return dados
3. Use Optional Corretamente
Optional[X] é equivalente a Union[X, None]. Use quando o valor pode ser None.
4. Configure a Ferramenta de Type Checking
Crie um arquivo pyproject.toml ou mypy.ini para configurar o mypy:
# mypy.ini
[mypy]
python_version = 3.12
strict = True
ignore_missing_imports = True
5. Documente Casos Complexos
Para tipos muito complexos, adicione comentários ou docstrings explicando a lógica.
Type Hints em Python 3.12 e 3.13
As versões mais recentes do Python trazem melhorias significativas no sistema de tipos:
- PEP 695 (Python 3.12): nova sintaxe para TypeVar e Generics, mais concisa.
- PEP 698 (Python 3.13): suporte aprimorado para
typeem type hints. - PEP 649 (Python 3.14, previsto): avaliação adiada de anotações por padrão, substituindo
from __future__ import annotations.
Fonte: PEP 695 - Type Parameter Syntax
Exemplo Completo: Sistema de Pedidos
Vamos aplicar tudo que aprendemos em um exemplo real:
from dataclasses import dataclass, field
from typing import List, Optional, Protocol, TypeVar
from datetime import datetime, date
from decimal import Decimal
T = TypeVar("T")
class Repositorio(Protocol[T]):
def salvar(self, entidade: T) -> None:
...
def buscar_por_id(self, id_: int) -> Optional[T]:
...
@dataclass
class Cliente:
id: int
nome: str
email: str
data_cadastro: date = field(default_factory=date.today)
@dataclass
class ItemPedido:
produto: str
quantidade: int
preco_unitario: Decimal
@dataclass
class Pedido:
id: int
cliente: Cliente
itens: List[ItemPedido]
data_criacao: datetime = field(default_factory=datetime.now)
status: str = "pendente"
def calcular_total(self) -> Decimal:
return sum(
item.quantidade * item.preco_unitario
for item in self.itens
)
def atualizar_status(self, novo_status: str) -> None:
self.status = novo_status
class RepositorioCliente:
def salvar(self, cliente: Cliente) -> None:
print(f"Salvando cliente {cliente.nome}")
def buscar_por_id(self, id_: int) -> Optional[Cliente]:
return Cliente(id=id_, nome="Ana", email="[email protected]")
def processar_pedido(
pedido: Pedido,
repositorio: Repositorio[Pedido]
) -> bool:
try:
repositorio.salvar(pedido)
pedido.atualizar_status("processado")
return True
except Exception:
return False
Uso
cliente = Cliente(id=1, nome="Maria", email="[email protected]")
item = ItemPedido(produto="Notebook", quantidade=1, preco_unitario=Decimal("4500.00"))
pedido = Pedido(id=100, cliente=cliente, itens=[item])
repo = RepositorioCliente()
print(f"Total do pedido: R$ {pedido.calcular_total():.2f}")
processar_pedido(pedido, repo) # type: ignore
Conclusão
Type Hints são uma ferramenta poderosa que tornam o Python mais adequado para projetos de todos os tamanhos. Eles melhoram a legibilidade do código, previnem bugs, facilitam a refatoração e melhoram a experiência de desenvolvimento com IDEs modernas.
Comece adotando tipos gradualmente em seus projetos — você verá a diferença na qualidade e manutenibilidade do código. E lembre-se: Type Hints são opcionais, mas as vantagens que eles trazem são enormes!
Continue aprendendo com a Universo Python. Explore mais conteúdos sobre Data Classes, FastAPI e outros tópicos avançados de Python.