Projetos orientados a objetos frequentemente precisam definir um contrato comum para várias implementações. Uma classe de pagamento deve expor processar(); um repositório precisa oferecer salvar() e buscar(); um plugin deve declarar métodos obrigatórios. O módulo abc no Python fornece classes base abstratas, métodos obrigatórios e suporte a subclasses virtuais para representar esses contratos no runtime.
Neste guia, você aprenderá a usar ABC, ABCMeta, abstractmethod, register(), __subclasshook__() e update_abstractmethods(). O conteúdo complementa nossos artigos sobre types no Python, inspect, singledispatch, descriptors e dataclasses.
O que é uma classe abstrata
Uma classe abstrata descreve comportamento esperado e pode fornecer implementação parcial, mas não deve ser instanciada enquanto possuir métodos abstratos não resolvidos.
from abc import ABC, abstractmethod
class Armazenamento(ABC):
@abstractmethod
def salvar(self, chave, valor):
pass
@abstractmethod
def buscar(self, chave):
passTentar criar Armazenamento() gera TypeError. Uma subclasse concreta precisa implementar todos os membros abstratos.
Criar uma implementação concreta
class ArmazenamentoMemoria(Armazenamento):
def __init__(self):
self._dados = {}
def salvar(self, chave, valor):
self._dados[chave] = valor
def buscar(self, chave):
return self._dados[chave]
repositorio = ArmazenamentoMemoria()
repositorio.salvar("usuario", "Ana")
print(repositorio.buscar("usuario"))Além de comunicar o contrato, a classe base impede a criação acidental de implementações incompletas.
ABC é uma classe auxiliar
ABC usa ABCMeta como metaclass. Herdar de ABC é o modo mais simples de declarar uma classe base abstrata.
Quando a classe já precisa de outra metaclass, pode existir conflito. Nesse caso, derive uma metaclass compatível de ABCMeta ou use composição em vez de misturar hierarquias complexas.
abstractmethod
@abstractmethod marca um método como obrigatório. Ele deve ser o decorator mais interno quando combinado com classmethod, staticmethod ou property.
class Fabrica(ABC):
@classmethod
@abstractmethod
def criar(cls, configuracao):
pass
@staticmethod
@abstractmethod
def validar(configuracao):
passA ordem importa porque abstractmethod precisa marcar o descriptor subjacente.
Propriedades abstratas
class Documento(ABC):
@property
@abstractmethod
def titulo(self):
passUma subclasse pode implementar a propriedade com outro @property.
class Relatorio(Documento):
def __init__(self, titulo):
self._titulo = titulo
@property
def titulo(self):
return self._tituloDecorators antigos como abstractproperty, abstractclassmethod e abstractstaticmethod existem por compatibilidade, mas as combinações modernas são preferíveis.
Métodos abstratos podem ter implementação
Um método abstrato não precisa conter apenas pass. Ele pode fornecer comportamento comum que subclasses chamam com super().
class Exportador(ABC):
@abstractmethod
def exportar(self, dados):
self._validar(dados)
def _validar(self, dados):
if not dados:
raise ValueError("dados vazios")
class ExportadorJSON(Exportador):
def exportar(self, dados):
super().exportar(dados)
return json.dumps(dados)Esse padrão cria um ponto final cooperativo para herança múltipla e evita duplicar validações.
Implementação parcial
Uma subclasse pode continuar abstrata.
class ArmazenamentoComLog(Armazenamento):
def salvar(self, chave, valor):
print("salvando", chave)
return self._salvar_impl(chave, valor)
@abstractmethod
def _salvar_impl(self, chave, valor):
passEla implementa parte do contrato e introduz uma nova obrigação para subclasses concretas.
Verificar abstração
A metaclass mantém o conjunto __abstractmethods__.
print(Armazenamento.__abstractmethods__)
print(ArmazenamentoMemoria.__abstractmethods__)Frameworks podem usar inspect.isabstract() para uma API mais clara.
ABCMeta diretamente
Também é possível declarar a metaclass explicitamente.
from abc import ABCMeta
class Servico(metaclass=ABCMeta):
@abstractmethod
def executar(self):
passEsse estilo é útil quando você precisa compreender ou combinar metaclasses, mas herdar de ABC costuma ser mais legível.
Subclasses virtuais
register() permite considerar uma classe existente como subclasse sem alterar sua herança.
class Leitor(ABC):
@abstractmethod
def ler(self):
pass
class LeitorLegado:
def ler(self):
return "dados"
Leitor.register(LeitorLegado)
print(issubclass(LeitorLegado, Leitor))
print(isinstance(LeitorLegado(), Leitor))O registro não adiciona métodos, não chama código da ABC e não força a implementação do contrato. Ele apenas altera as respostas de issubclass() e isinstance().
Risco das subclasses virtuais
Registrar uma classe incorreta produz um falso senso de compatibilidade.
class Incompleto:
pass
Leitor.register(Incompleto)
print(isinstance(Incompleto(), Leitor)) # TrueUse registro somente quando o tipo já implementa semanticamente o contrato. Testes de conformidade continuam necessários.
register() como decorator
O método devolve a classe registrada e pode ser usado como decorator.
@Leitor.register
class OutroLeitor:
def ler(self):
return "outro"Esse formato deixa a intenção próxima da implementação, mas também cria dependência direta da ABC.
__subclasshook__()
Uma ABC pode reconhecer subclasses estruturalmente sem registro explícito.
class IteravelPersonalizado(ABC):
@classmethod
def __subclasshook__(cls, C):
if cls is IteravelPersonalizado:
if any("__iter__" in B.__dict__ for B in C.__mro__):
return True
return NotImplementedAgora classes que definem __iter__ diretamente na hierarquia podem ser reconhecidas por issubclass().
Retornar True, False ou NotImplemented
__subclasshook__() pode retornar:
True: considerar subclasse;False: rejeitar mesmo se outras regras sugerirem o contrário;NotImplemented: continuar o algoritmo normal.
Prefira NotImplemented quando o hook não consegue decidir. Retornar False é uma decisão forte.
Hooks simples e conservadores
Verificar apenas a presença de um nome não garante assinatura, semântica ou tipos corretos. Um método ler pode fazer algo completamente diferente.
Use hooks para protocolos bem conhecidos e fáceis de reconhecer. Para contratos ricos, herança explícita, Protocol e testes são mais seguros.
ABC versus Protocol
Classes base abstratas trabalham principalmente com herança nominal e verificações de runtime. typing.Protocol descreve tipagem estrutural para ferramentas estáticas.
from typing import Protocol
class PodeLer(Protocol):
def ler(self) -> str: ...Uma classe com método compatível satisfaz o protocolo para o type checker sem herdar dele. Protocols com @runtime_checkable permitem verificações limitadas em runtime, mas não validam assinaturas profundamente.
ABC versus duck typing
Python permite simplesmente chamar objeto.ler() e tratar AttributeError quando não existe. Essa abordagem é flexível e reduz acoplamento.
Use uma ABC quando há valor em documentação central, implementação compartilhada, construção impedida para classes incompletas ou registro com isinstance().
Herança múltipla
Múltiplas ABCs podem compor contratos.
class Legivel(ABC):
@abstractmethod
def ler(self): ...
class Gravavel(ABC):
@abstractmethod
def gravar(self, dados): ...
class ArquivoVirtual(Legivel, Gravavel):
def ler(self):
return self.dados
def gravar(self, dados):
self.dados = dadosMantenha métodos cooperativos com super() e evite estado conflitante nas bases.
Atualizar métodos abstratos dinamicamente
Adicionar uma implementação após a criação da classe não atualiza automaticamente o conjunto de abstratos. Use update_abstractmethods().
from abc import update_abstractmethods
class Dinamica(ABC):
@abstractmethod
def executar(self):
pass
Dinamica.executar = lambda self: "ok"
update_abstractmethods(Dinamica)
print(Dinamica().executar())A documentação oficial de abc recomenda essa função para alterações dinâmicas. Ela assume que as superclasses já foram atualizadas.
Cache das verificações
ABCMeta mantém caches internos para acelerar issubclass(). get_cache_token() devolve um token que muda quando o registro de subclasses virtuais é alterado.
from abc import get_cache_token
token_antes = get_cache_token()
Leitor.register(NovoLeitor)
token_depois = get_cache_token()
assert token_antes != token_depoisFrameworks que mantêm caches próprios de conformidade podem invalidá-los quando o token muda.
Plugins
Uma ABC pode ser a base de plugins concretos.
class Plugin(ABC):
nome: str
@abstractmethod
def iniciar(self, contexto):
pass
@abstractmethod
def encerrar(self):
passAo carregar um plugin, verifique se a classe é concreta, valide metadados e trate falhas de inicialização. Herança não fornece isolamento ou segurança.
Dependência e testes
Receber uma ABC no construtor torna o contrato explícito.
class Aplicacao:
def __init__(self, armazenamento: Armazenamento):
self.armazenamento = armazenamentoEm testes, implemente um fake pequeno que satisfaça o contrato. Evite mocks excessivamente permissivos que aceitariam métodos inexistentes.
Alterações de API
Adicionar um novo método abstrato quebra todas as subclasses concretas existentes. É uma mudança incompatível.
Antes de tornar um método obrigatório, considere fornecer implementação padrão, criar uma ABC nova ou introduzir uma capability separada.
Erros frequentes
- Colocar
@abstractmethodfora de@classmethodou@property. - Presumir que uma subclasse virtual recebeu métodos.
- Registrar classes que não cumprem o contrato.
- Usar hooks estruturais amplos demais.
- Adicionar métodos abstratos sem planejar compatibilidade.
- Criar hierarquias profundas para compartilhar pouco código.
- Confundir ABC com sandbox de plugins.
- Modificar a classe sem
update_abstractmethods().
Boas práticas
- Mantenha contratos pequenos e coesos.
- Forneça implementação comum quando útil.
- Use
super()em herança cooperativa. - Registre subclasses virtuais somente após testes.
- Prefira
NotImplementedem hooks incertos. - Compare ABCs com Protocols e duck typing.
- Trate novos abstratos como mudança incompatível.
- Teste subclasses concretas com uma suíte compartilhada.
Conclusão
O módulo abc no Python permite definir contratos de runtime, impedir instanciação de classes incompletas e compartilhar implementação entre subclasses. ABC e abstractmethod cobrem a maioria dos casos, enquanto registro virtual e __subclasshook__() oferecem integração estrutural.
Classes abstratas funcionam melhor quando o contrato é pequeno, estável e semanticamente claro. Com hooks conservadores, testes de conformidade e cuidado ao evoluir a API, ABCs tornam extensões e dependências explícitas sem transformar o projeto em uma hierarquia rígida.





