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 type em 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.