O Modelo Mental de Verificação de Tipos
Dicas de tipo são anotações que você anexa a variáveis, parâmetros e valores de retorno.
Elas descrevem a forma dos dados que um pedaço de código espera e produz.
O próprio interpretador faz quase nada com elas em tempo de execução.
O trabalho real acontece em um programa separado, um verificador de tipo estático como mypy ou pyright, que lê seu código-fonte, constrói um modelo do tipo de cada valor e relata onde esse modelo falha.
Noções Básicas de Dicas de Tipo mostra a sintaxe; Protocolos e Tipagem Estrutural, Genéricos e TypeVar e Estratégia de Tipagem Gradual cobrem cada faceta em profundidade.
Esta página é a camada abaixo de todas elas: o que um verificador realmente está fazendo e por que isso é um tipo fundamentalmente diferente de segurança do que o interpretador fornece.
Resumo
- Uma dica de tipo é um metadado lido por um verificador de tipo estático externo, não uma restrição em tempo de execução imposta pelo próprio interpretador Python.
- Por que Importa: Permite que uma equipe capture uma classe inteira de bugs de incompatibilidade de forma - argumento errado,
Nonenão tratado, um atributo com erro de digitação - antes que o código seja executado, sem abrir mão do modelo de objeto dinâmico do Python. - Conceitos Chave: anotação, verificador de tipo estático, tipagem gradual, tipagem estrutural, tipagem nominal, type erasure (remoção de tipo).
- Quando Usar: Assinaturas de funções públicas, dados que cruzam limites de módulos ou serviços, qualquer base de código com mais de um contribuidor, ou onde quer que uma classe de bug recorra devido a um valor de forma incorreta.
- Limitações / Trade-offs: As dicas adicionam zero segurança em tempo de execução por si só, o modelo de um verificador de um programa dinâmico pode estar incorreto ou incompleto, e o modo estrito tem atrito real contra código legitimamente dinâmico.
- Tópicos Relacionados: Configuração de mypy e pyright, tipagem estrutural baseada em Protocolo, genéricos, estratégia de adoção gradual.
Fundamentos
Python lançou a sintaxe de anotação (x: int, def f() -> str) anos antes de ter um significado oficial.
PEP 484 deu a essa sintaxe um propósito: um vocabulário padrão que ferramentas externas poderiam ler para raciocinar sobre os tipos de um programa sem executá-lo.
Crucialmente, o comportamento do interpretador não mudou.
def add(a: int, b: int) -> int: return a + b é executado exatamente da mesma forma, quer você chame add(1, 2) ou add("x", "y") - as anotações são armazenadas em __annotations__ e, de outra forma, inertes em tempo de execução.
Uma analogia útil é uma planta de construção revisada por um inspetor antes do início da construção.
A aprovação do inspetor (uma execução limpa do mypy) informa que o projeto é sólido no papel.
Isso não impede fisicamente uma equipe de construção de construir algo diferente da planta - o runtime do Python é essa equipe, e ela nunca consulta a planta enquanto trabalha.
É por isso que a abordagem do Python é chamada de tipagem gradual: uma base de código pode misturar módulos totalmente anotados, módulos parcialmente anotados e código legado completamente não anotado, e o verificador raciocina sobre cada parte com as informações que possui, recorrendo a um Any implícito onde quer que as dicas estejam faltando.
Essa única escolha de design é o que torna a adição de tipos a uma base de código grande e existente tratável, em vez de uma reescrita completa.
Mecânicas e Interações
O verdadeiro trabalho de um verificador de tipo é a inferência estática: percorrer seu código-fonte sem executá-lo, atribuir um tipo a cada expressão e sinalizar locais onde uma operação é inconsistente com os tipos que ele inferiu.
Duas maneiras diferentes de decidir "este valor satisfaz esse tipo" são importantes aqui, e misturá-las é a fonte mais comum de confusão nesta seção.
A tipagem nominal pergunta sobre ancestralidade - um valor satisfaz um tipo se sua classe for esse tipo ou uma subclasse dele, verificado através da cadeia de herança explícita.
A tipagem estrutural pergunta sobre a forma em vez disso - um valor satisfaz um tipo se ele tiver os métodos e atributos corretos, independentemente de qual classe ele realmente é ou do que ele herda.
A hierarquia de classes embutida do Python é nominal por padrão, mas Protocol (PEP 544) adiciona tipagem estrutural por cima, que é por que um Protocol nunca exige que as classes que o satisfazem herdem de algo.
Runtime (o que realmente executa) Camada estática (o que mypy/pyright vê)
----------------------------------- --------------------------------------
def greet(name: str) -> str: greet: (str) -> str
return f"Hi {name}" name inferido como str dentro do corpo
greet(42) # executa, imprime "Hi 42" greet(42) # MARCADO: int não é str
A lacuna nesse diagrama é o ponto principal: o runtime nunca impede greet(42), mas o modelo do programa do verificador o faz, porque ele raciocina puramente a partir de tipos declarados e inferidos, nunca a partir do que uma determinada execução realmente faz.
Genéricos estendem essa mesma maquinaria de inferência para formas paramétricas - um list[int] e um list[str] são tipos diferentes para o verificador, mesmo que ambos sejam, em tempo de execução, exatamente a mesma classe list.
TypeVar e a sintaxe type do PEP 695 permitem que você escreva uma função ou classe cujos tipos estão vinculados entre os parâmetros (um TypeVar vinculado ao tipo do elemento de um contêiner, por exemplo), para que o verificador possa verificar que o que quer que saia de uma função genérica é consistente com o que entrou, sem que você escreva essa função nove vezes para nove tipos concretos.
Considerações Avançadas e Aplicações
Como a inferência é tão boa quanto as informações disponíveis, os verificadores expõem dials de rigor em vez de um único interruptor on/off - disallow_untyped_defs, warn_return_any, modo strict, substituições por módulo em pyproject.toml.
Uma base de código grande quase nunca adota o modo estrito completo em todos os lugares de uma vez; em vez disso, ela aperta dial por dial, módulo por módulo, que é todo o assunto de Estratégia de Tipagem Gradual.
Any é o escape hatch designado do verificador, e ele merece respeito especial porque é contagioso: uma vez que um valor é tipado como Any, toda expressão construída a partir dele também é implicitamente Any, desativando silenciosamente a verificação para tudo a jusante até que uma fronteira o reanote.
Verificação de tipo estática e validação em tempo de execução também são fáceis de confundir, mas resolvem problemas diferentes.
Um verificador como mypy prova que seu código é internamente consistente, dados os tipos que você declarou; ele não pode verificar se os dados que chegam via HTTP ou de um arquivo realmente correspondem a esses tipos, porque esses dados não existem quando o verificador é executado.
Essa é precisamente a lacuna que bibliotecas como Pydantic preenchem - validação em tempo de execução na fronteira, tipos estáticos em todos os lugares atrás dela - e por que TypedDict documenta uma forma JSON para benefício do verificador sem validar nada quando o programa realmente é executado.
| Abordagem | Força | Fraqueza | Melhor Ajuste |
|---|---|---|---|
| Tipagem nominal (hierarquias de classe, ABCs) | Intenção explícita, amigável para IDE, funciona bem para árvores de classe próprias | Requer herança; estranho para código de terceiros ou objetos ad-hoc | Seu próprio modelo de domínio com uma relação real de é-um |
Tipagem estrutural (Protocol) | Nenhuma herança necessária; se encaixa bem em código duck-typed e de terceiros | Fácil de subespecificar uma forma; menos descobrível do que uma classe base concreta | Portas/interfaces, pontos de teste, adaptação de tipos externos |
| Validação em tempo de execução (Pydantic) | Realmente impõe a forma quando o programa é executado, com mensagens de erro reais | Custo de tempo de execução; uma segunda definição de esquema para manter em sincronia com os tipos estáticos | Dados cruzando uma fronteira não confiável - HTTP, arquivos, filas |
Sem dicas (Any / não tipado) | Zero atrito, mais rápido para prototipar | Nenhuma garantia estática; bugs surgem como tracebacks de produção | Scripts descartáveis, notebooks exploratórios |
A tipagem estática em Python também continuou evoluindo em vez de ficar parada: a sintaxe de união X | Y do PEP 604, a sintaxe genérica mais concisa do PEP 695, e cada lançamento de mypy/pyright que refina mais padrões match/isinstance fazem parte do fechamento da lacuna entre o que o sistema de tipos pode expressar e o que o Python idiomático já parece.
Equívocos Comuns
- "Dicas de tipo tornam Python uma linguagem de tipagem estática." O runtime permanece exatamente tão dinâmico quanto antes; apenas uma ferramenta separada e opcional impõe algo, e apenas quando você a executa.
- "mypy ou pyright passando significa que o código não pode falhar com entrada incorreta." Uma execução limpa do verificador prova a consistência interna dada os tipos que você declarou ou que o verificador inferiu, não que os dados que chegam em tempo de execução realmente correspondem a esses tipos - esse é um problema de validação, não de análise estática.
- "
Anyé uma maneira segura e neutra de silenciar um erro do verificador."Anydesativa a verificação para esse valor e tudo o que é derivado dele, então um únicoAnyem uma fronteira pode apagar silenciosamente a segurança de tipo através de toda uma cadeia de chamadas. - "
Protocolé apenas uma classe base abstrata com um decorador extra." UmProtocolcorresponde por forma, nunca por herança - uma classe o satisfaz apenas por ter os métodos corretos, sem a necessidade declass Foo(MyProtocol)em qualquer lugar. - "O modo estrito é tudo ou nada, então não vale a pena começar." O rigor é um conjunto de dials independentes, por módulo; uma base de código pode apertar um pacote de cada vez indefinidamente sem nunca virar um único interruptor global.
FAQs
As dicas de tipo mudam a forma como meu código é executado?
Não. O interpretador armazena anotações em __annotations__ e, de outra forma, as ignora; o comportamento em tempo de execução é idêntico com ou sem dicas.
O que realmente lê as dicas de tipo, então?
Um verificador de tipo estático separado - mypy ou pyright são os dois dominantes - lê seu código-fonte sem executá-lo e relata inconsistências com os tipos que você declarou ou que ele inferiu.
O que significa "tipagem gradual" na prática?
Significa que código tipado e não tipado podem coexistir na mesma base de código. Código não anotado é tratado como implicitamente Any, então um verificador ainda pode analisar as partes que têm dicas sem exigir que todo o programa seja tipado de uma vez.
Qual é a diferença entre tipagem estrutural e nominal?
A tipagem nominal verifica a ancestralidade da classe - um valor corresponde a um tipo se sua classe for esse tipo ou uma subclasse. A tipagem estrutural (via Protocol) verifica a forma em vez disso - um valor corresponde se ele tiver os métodos e atributos corretos, independentemente da herança.
Por que `Any` é considerado arriscado se não causa erros?
Porque é contagioso. Toda expressão derivada de um valor Any também é implicitamente Any, então a verificação é silenciosamente desativada para toda uma cadeia a jusante a partir de um único valor não anotado ou explicitamente Any.
Um verificador de tipo pode capturar um bug que só aparece com certos dados de entrada?
Não diretamente. Um verificador raciocina a partir de tipos declarados e inferidos, não de valores reais em tempo de execução, então ele não pode capturar um bug causado por, digamos, JSON malformado de uma API - isso requer validação em tempo de execução, não análise estática.
Preciso de Pydantic se já uso dicas de tipo em todos os lugares?
Para valores internos, já tipados, dicas mais um verificador geralmente são suficientes. Para dados que entram no programa de fora - corpos HTTP, arquivos de configuração, mensagens de fila - você precisa de algo que valide em tempo de execução, já que o verificador nunca vê esses dados antes de serem executados.
Por que `list[int]` importa se `list` funciona bem em tempo de execução?
Em tempo de execução não há diferença - ambos são a mesma classe list. Estaticamente, no entanto, list[int] informa ao verificador o que está dentro, para que ele possa capturar código que tenta anexar uma string ou iterar assumindo o tipo de elemento errado.
Adotar o modo estrito é uma decisão tudo ou nada?
Não. Configurações de rigor como disallow_untyped_defs ou warn_return_any podem ser habilitadas por módulo em pyproject.toml, que é como a maioria das bases de código migra - apertando um pacote de cada vez em vez de virar um único interruptor global.
Por que Python oferece duas filosofias de tipagem separadas (Protocol vs classes) em vez de uma?
Porque o código real precisa de ambas. Seu próprio modelo de domínio frequentemente tem relacionamentos de herança genuínos que valem a pena expressar nominalmente, enquanto objetos de terceiros ou duck-typed precisam ser descritíveis apenas por forma - Protocol existe especificamente para cobrir esse segundo caso sem forçar a herança em código que você não possui.
Um verificador de tipo que passa significa que meu código está livre de bugs?
Não - ele apenas prova que o código é internamente consistente com os tipos que você declarou ou que o verificador inferiu. Erros de lógica, incompatibilidades de dados em tempo de execução e qualquer coisa fora do vocabulário do sistema de tipos ainda podem passar.
Por que Python adicionou a sintaxe `X | Y` em vez de manter `Union[X, Y]`?
É mais curto e lê mais próximo de como as pessoas já descrevem tipos em prosa, e reflete uma tendência mais ampla de fechar a lacuna entre o que o sistema de tipos pode expressar e como o Python idiomático é realmente escrito.
Relacionados
- Noções Básicas de Dicas de Tipo - sintaxe e exemplos práticos de anotação
- Protocolos e Tipagem Estrutural - tipagem baseada em forma em profundidade
- Genéricos e TypeVar - tipos paramétricos e sintaxe PEP 695
- Estratégia de Tipagem Gradual - adoção de tipos em uma base de código existente
- Configuração de mypy & pyright - configuração dos verificadores descritos nesta página
- Melhores Práticas de Dicas de Tipo - onde os tipos compensam na prática
Versões de Stack: Esta página foi escrita para Python 3.14.0 (estável 3.14, manutenção 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+ e uv 0.6+.