Curso de Python / Usar type hints e comparar tipos com validação em execução

Tipos anotados, ou type hints, descrevem os valores esperados por variáveis e funções. O interpretador Python comum não os impõe automaticamente: use a análise estática para conferir relações no código e validação explícita para rejeitar entradas inadequadas durante a execução.

Ler uma anotação como um contrato

amount: int informa que se espera um inteiro; não chama int nem transforma uma string em número. -> str descreve o resultado esperado da função. As anotações ajudam leitores, editores e verificadores a identificar chamadas incompatíveis antes de executar o programa.

list[int] indica uma lista de inteiros. dict[str, str] indica chaves e valores string. A união str | None informa que o resultado pode ser uma string ou a ausência de um valor. Esta prática adota Python 3.12 ou posterior. A avaliação dinâmica de anotações pode mudar entre versões; confira as regras da versão usada se seu programa inspecionar essas informações em tempo de execução.

A documentação de typing e dicas de tipo distingue essas anotações da imposição automática de tipos. Uma regra de domínio, como não aceitar despesas negativas, continua exigindo uma decisão explícita mesmo quando o parâmetro é anotado como inteiro.

Comparar a anotação com uma verificação explícita

Salve o programa completo abaixo como hints.py, em UTF-8, numa pasta de prática. Execute python hints.py no terminal com o ambiente virtual do projeto selecionado. Fora de um ambiente virtual no macOS ou Ubuntu, use python3 se esse for o comando do interpretador. A execução deste programa usa apenas recursos da linguagem.

def format_amount(amount: int) -> str:
    if type(amount) is not int or amount < 0:
        raise ValueError("invalid amount")
    return f"{amount:,} KRW"

def find_name(names: dict[str, str], key: str) -> str | None:
    return names.get(key)

print(format_amount(8000))
print(find_name({"e1": "lunch"}, "missing"))
try:
    format_amount("8000")
except ValueError:
    print("rejected")

Resultado esperado:

8,000 KRW
None
rejected

O formato original foi mantido: 8,000 KRW usa vírgula como separador de milhares e o código de moeda do won sul-coreano. Essa string não é uma conversão de moeda nem formatação segundo a localidade brasileira. find_name devolve None porque a chave procurada não existe no dicionário.

A última chamada passa a string "8000" de propósito. O teste dentro de format_amount a rejeita com ValueError; o bloco de tratamento imprime rejected, isto é, “rejeitado”. Esse resultado vem do if e do tratamento da exceção, não da anotação int. A verificação também rejeita booleanos e números negativos.

Executar a análise estática com mypy

Para comparar os dois mecanismos, selecione um ambiente virtual da prática e execute estes comandos no terminal. A primeira linha instala o verificador nesse ambiente; a segunda analisa o arquivo, sem executar as funções do programa.

python -m pip install mypy
python -m mypy hints.py

O arquivo original deve gerar um diagnóstico de argumento incompatível: a chamada format_amount("8000") oferece uma string onde o contrato pede um inteiro. A forma exata da mensagem depende da versão do mypy. O fato de a exceção ser capturada em tempo de execução não torna essa chamada compatível com o contrato estático.

Para comparar com um caso válido, faça uma cópia chamada hints_valid.py. Nela, remova todo o bloco final try/except que contém a chamada intencionalmente errada, mantendo as definições e os dois primeiros print. Rode python -m mypy hints_valid.py: a análise deve terminar sem erros. Se executar essa cópia, ela imprime somente 8,000 KRW e None.

Outra correção é fornecer um inteiro, quando isso corresponde à intenção. Para dados externos, decidir se a string deve ser convertida ou rejeitada é uma política de entrada. Não transforme todas as anotações em Any apenas para silenciar um diagnóstico: isso reduz a informação disponível para o verificador. Consulte o guia oficial de instalação e primeiros passos do mypy.

Camada O que verifica Limite
Análise estática Relações entre tipos no código analisado Não comprova que um arquivo exista ou que a rede funcione
Validação em execução Valores efetivamente recebidos, intervalos e campos obrigatórios Precisa implementar as regras relevantes da aplicação

Tratar valores ausentes e regras de entrada

Antes de chamar um método como upper() no resultado de find_name, verifique se o valor é diferente de None. Uma condição if name is not None: permite usar o texto nesse ramo e reservar o outro para o caso ausente. Não use apenas a suposição de que a chave “normalmente existe”. A documentação de tipos opcionais e None no mypy explica esse tratamento.

A função find_name não inspeciona os tipos internos de um dicionário recebido. Se algum código ignorar as dicas e passar um valor numérico no lugar do nome, a anotação não o converterá em string. A entrada precisa ser validada antes de chegar à função, conforme o contrato da aplicação.

  1. Troque a chave procurada por e1 e confirme que o resultado passa a lunch. Restaure a chave ausente e confirme None.
  2. Troque a chamada errada por um inteiro negativo. O tipo é compatível com a anotação, mas a validação de domínio ainda deve rejeitá-lo. Compare essa diferença com o erro estático da string.
  3. Teste zero e um inteiro positivo em format_amount; ambos devem produzir strings. Restaure o arquivo original para recuperar as três linhas apresentadas.

Compare cada caminho de retorno com a anotação e verifique os casos ausente, válido e inválido. A análise estática e os testes em execução fornecem evidências diferentes e complementares; uma camada não substitui a outra.