O decorator typing.override ajuda a deixar explícito que um método de uma subclasse substitui um método definido em uma classe base. Disponível na biblioteca padrão a partir do Python 3.12, ele não muda o comportamento do programa em tempo de execução: sua principal função é melhorar a análise estática, a revisão de código e a manutenção de hierarquias orientadas a objetos.
Em projetos pequenos, sobrescrever um método pode parecer trivial. Em bases grandes, porém, uma simples alteração no nome ou na assinatura do método da classe base pode fazer a subclasse deixar de sobrescrever o método esperado. O programa ainda pode executar, mas a regra de negócio muda silenciosamente. É exatamente esse tipo de problema que @override ajuda ferramentas como Pyright, mypy e IDEs modernas a detectar.
Exemplo básico
from typing import override
class Notificador:
def enviar(self, mensagem: str) -> None:
print(mensagem)
class NotificadorEmail(Notificador):
@override
def enviar(self, mensagem: str) -> None:
print(f"E-mail: {mensagem}")
O decorator informa ao analisador: “este método deve existir em alguma classe ancestral”. Caso o nome seja digitado incorretamente, como envair, a ferramenta pode apontar que não existe nenhum método correspondente para sobrescrever. Sem essa declaração, o erro pode passar despercebido porque a subclasse apenas ganhou um método novo.
Por que usar typing.override
O primeiro benefício é a documentação executável. Quem lê a classe percebe imediatamente que o método pertence a um contrato herdado. Isso reduz a necessidade de navegar pela árvore de herança apenas para entender a intenção do código.
O segundo benefício é a segurança durante refatorações. Imagine que Notificador.enviar seja renomeado para notificar. As subclasses marcadas com @override passam a gerar alertas, mostrando exatamente quais implementações precisam ser atualizadas.
O terceiro benefício é a validação de assinatura. Analisadores estáticos podem comparar parâmetros, tipos e retorno com o método original. Mudanças incompatíveis ficam visíveis antes de chegarem à produção.
Compatibilidade de assinaturas
Uma sobrescrita deve respeitar o princípio de substituição: uma instância da subclasse deve poder ser usada onde a classe base é esperada. Por isso, tornar parâmetros mais restritivos normalmente é problemático.
from typing import override
class Repositorio:
def salvar(self, dado: object) -> bool:
return True
class RepositorioTexto(Repositorio):
@override
def salvar(self, dado: str) -> bool:
return bool(dado)
Nesse exemplo, a classe base aceita qualquer object, enquanto a subclasse aceita apenas str. Um analisador pode rejeitar a implementação porque código que conhece apenas Repositorio poderia chamar salvar(123). A subclasse quebraria o contrato.
Já o tipo de retorno pode frequentemente ser mais específico. Se a classe base retorna Animal, uma subclasse pode retornar Cachorro, desde que Cachorro seja subtipo de Animal.
Métodos abstratos
@override funciona muito bem com abc.ABC e @abstractmethod. O método abstrato define a obrigação; @override mostra que a implementação concreta atende a essa obrigação.
from abc import ABC, abstractmethod
from typing import override
class Conversor(ABC):
@abstractmethod
def converter(self, valor: str) -> int:
raise NotImplementedError
class ConversorDecimal(Conversor):
@override
def converter(self, valor: str) -> int:
return int(valor, 10)
Essa combinação deixa a arquitetura clara: a classe base declara o contrato e a classe concreta declara sua intenção de implementá-lo.
Properties, classmethods e staticmethods
O decorator também pode ser usado ao substituir propriedades e outros descritores. A ordem dos decorators merece atenção. Em geral, coloque @override abaixo de @classmethod, @staticmethod ou @property, diretamente sobre a função que está sendo marcada.
from typing import override
class Documento:
@property
def formato(self) -> str:
return "genérico"
class PDF(Documento):
@property
@override
def formato(self) -> str:
return "pdf"
Ferramentas podem variar em detalhes de suporte, por isso vale manter o analisador e a IDE atualizados.
Herança múltipla
Em herança múltipla, @override não informa qual ancestral está sendo substituído. Ele apenas afirma que existe um membro compatível na resolução de métodos. Para entender qual implementação será chamada com super(), ainda é necessário conhecer a MRO, a ordem de resolução de métodos.
class Logavel:
def executar(self) -> None:
print("log")
class Auditavel:
def executar(self) -> None:
print("auditoria")
class Servico(Logavel, Auditavel):
@override
def executar(self) -> None:
super().executar()
print("serviço")
A chamada de super() segue a MRO de Servico. O decorator não altera essa ordem.
Protocolos e interfaces estruturais
Um ponto importante é que @override foi pensado para relações de herança. Implementar informalmente a mesma assinatura de um Protocol sem herdar dele pode não ser considerado uma sobrescrita. Protocolos trabalham com tipagem estrutural: o objeto é compatível porque possui os membros esperados, não necessariamente porque deriva da interface.
Quando a intenção é apenas provar compatibilidade com um protocolo, prefira anotações de tipo e testes estáticos. Use @override quando houver uma classe ancestral real com o membro correspondente.
Compatibilidade com versões antigas
Para projetos que ainda suportam Python 3.11 ou anterior, use typing_extensions.override. O pacote typing_extensions disponibiliza recursos novos de tipagem para versões anteriores.
try:
from typing import override
except ImportError:
from typing_extensions import override
Em bibliotecas, também é possível declarar a dependência mínima de typing_extensions e importar sempre do mesmo lugar.
O decorator não substitui testes
@override detecta inconsistências estruturais, mas não valida a lógica do método. Uma implementação pode ter assinatura perfeita e ainda calcular o resultado errado. Portanto, combine análise estática, testes unitários e testes de integração.
Também é útil configurar o analisador para exigir @override em toda sobrescrita. Algumas ferramentas oferecem uma regra que alerta quando um método substitui outro sem o decorator. Isso torna o padrão consistente no projeto.
Boas práticas
Adote @override principalmente em frameworks internos, plugins, adaptadores, repositórios, serviços e classes com várias subclasses. Evite hierarquias profundas apenas porque o decorator existe; composição ainda costuma ser uma solução mais simples.
Mantenha assinaturas compatíveis, preserve invariantes da classe base e documente diferenças comportamentais. Quando chamar super(), saiba se a implementação ancestral precisa ser executada antes ou depois da lógica especializada.
Para aprofundar temas relacionados, consulte nossos guias sobre introspecção com inspect, dataclass_transform, TypeVarTuple e StrEnum. A documentação oficial de typing.override e a PEP 698 são as referências externas principais.
Conclusão
typing.override é um recurso pequeno, mas valioso. Ele transforma uma intenção implícita em uma declaração verificável, melhora revisões, reduz falhas de refatoração e documenta contratos de herança. Em projetos orientados a objetos, usar o decorator de forma consistente torna a evolução das classes mais segura sem adicionar custo relevante em tempo de execução.







