NewType: A Arma Secreta do Python Contra Bugs Lógicos
Pare de gerar classes sem motivos no seu código só para ter um novo tipo. NewType foi projetado exatamente para isso com praticamente zero overhead.
Em qualquer aplicação um pouco mais complexa, lidamos com diferentes tipos de identificadores: user_id, product_id, session_id… Do ponto de vista do Python, muitas vezes todos eles são a mesma coisa: um simples int, uuid, str…. E é aí que mora o perigo.
O que impede um desenvolvedor (ou você mesmo, num dia cansado) de passar acidentalmente um product_id para uma função que esperava um user_id? Nada.
Para o type checker, um int é um int, e a verificação passa sem alarde. Mas em produção, esse erro pode causar um bug silencioso e catastrófico.
Como podemos ensinar ao nosso type checker a diferença semântica entre tipos que têm a mesma estrutura? A resposta é uma ferramenta elegante e poderosa do módulo typing: o NewType.
Tenho vídeo sobre isso também se quiser:
A Segurança dos Tipos Nominais, com Zero Overhead
Diferente de um TypeAlias (como type UserID = int), que é apenas um apelido, o NewType cria um tipo nominal distinto que existe apenas para o sistema de análise estática.
from typing import NewType
# Criamos dois tipos distintos para o type checker
UserId = NewType("UserId", int)
PostId = NewType("PostId", int)
def get_user(user_id: UserId) -> None:
...
# O Pyright agora nos protege!
user = UserId(123)
post = PostId(456)
get_user(user) # ✅ Válido
# get_user(post) # ❌ ERRO! O type checker impede a chamada.
Com essa simples mudança, criamos uma barreira de segurança que não existia antes.
E o melhor de tudo? NewType tem praticamente zero impacto na performance, pois ele não cria novas classes em tempo de execução.
A "Pegadinha" do Runtime: A Função de Identidade
E aqui vem o comportamento mais contra-intuitivo e importante de se entender: em runtime, NewType é uma “farsa”. Ele atua como a função de identidade, simplesmente retornando o valor que recebeu, sem nenhuma alteração.
meu_id = UserId(123)
print(type(meu_id))
# Saída: <class 'int'>
Isso significa que o NewType não faz nenhuma validação ou conversão em tempo de execução. Sua magia acontece inteiramente durante a fase de desenvolvimento, guiando o programador para o código correto.
Conectando com o Mundo Real: typing.cast
Ok, mas e quando recebemos um int puro de uma fonte externa, como um banco de dados ou uma API? O type checker vai reclamar se tentarmos passar esse int para nossa função get_user.
A ponte entre o mundo "não-tipado" e o nosso código seguro é o typing.cast. Com ele, nós dizemos ao type checker: "Eu, o desenvolvedor, garanto que este int representa um UserId. Confie em mim."
from typing import cast
raw_id_from_db = 123 # Veio como um int puro
user_id = cast(UserId, raw_id_from_db)
get_user(user_id) # ✅ Agora a chamada é válida!
O cast é nossa ferramenta para assumir a responsabilidade e informar ao sistema de tipos sobre o conhecimento que temos do nosso domínio de negócio.
Para ver a demonstração completa, com mais exemplos práticos e todas as nuances do NewType, confira a aula completa no YouTube!
Assista à Aula 15 completa no YouTube.
Até a próxima…


