ABC e classes abstratas
1. Introdução às Classes Abstratas
Classes abstratas são um conceito fundamental no design orientado a objetos que estabelece contratos entre classes. Diferentemente das classes concretas, que podem ser instanciadas diretamente, as classes abstratas servem como modelos para outras classes, definindo uma interface que as subclasses devem implementar.
Em Python, as classes abstratas desempenham um papel crucial na criação de código polimórfico e padronizado. Elas permitem que desenvolvedores definam métodos que obrigatoriamente devem ser implementados pelas subclasses, garantindo que todas as classes derivadas sigam o mesmo contrato. Isso é especialmente útil em projetos grandes e bibliotecas, onde a consistência da interface é essencial.
A principal diferença entre classes concretas e abstratas está na capacidade de instanciação: enquanto você pode criar objetos de classes concretas, tentar instanciar uma classe abstrata resultará em erro. Além disso, classes abstratas podem conter tanto métodos abstratos (sem implementação) quanto métodos concretos (com implementação).
2. O Módulo abc e a Classe ABC
Para trabalhar com classes abstratas em Python, utilizamos o módulo abc (Abstract Base Classes). A classe base ABC fornecida por este módulo serve como ponto de partida para definir nossas próprias classes abstratas.
from abc import ABC
class MinhaClasseAbstrata(ABC):
pass
# Esta classe não possui métodos abstratos, mas ainda assim é considerada abstrata
Embora a classe acima não tenha métodos abstratos, herdar de ABC já a torna uma classe abstrata conceitualmente. No entanto, o verdadeiro poder das ABCs vem com o uso de métodos abstratos.
3. Métodos Abstratos com @abstractmethod
O decorador @abstractmethod é a ferramenta principal para definir métodos que devem ser obrigatoriamente implementados pelas subclasses. Qualquer classe que herde de uma ABC e não implemente todos os seus métodos abstratos não poderá ser instanciada.
from abc import ABC, abstractmethod
class Forma(ABC):
@abstractmethod
def area(self):
pass
class Quadrado(Forma):
def __init__(self, lado):
self.lado = lado
def area(self):
return self.lado ** 2
class Circulo(Forma):
def __init__(self, raio):
self.raio = raio
def area(self):
return 3.14159 * self.raio ** 2
# Uso correto
quadrado = Quadrado(5)
print(quadrado.area()) # 25
# Erro: não é possível instanciar classe abstrata
# forma = Forma() # TypeError: Can't instantiate abstract class Forma with abstract methods area
Se uma subclasse não implementar todos os métodos abstratos, o Python levantará um TypeError ao tentar instanciá-la:
class TrianguloIncompleto(Forma):
pass
# triangulo = TrianguloIncompleto() # TypeError
4. Propriedades e Métodos de Classe Abstratos
O módulo abc também permite definir propriedades e métodos estáticos/de classe como abstratos:
from abc import ABC, abstractmethod
class Animal(ABC):
@property
@abstractmethod
def som(self):
pass
@classmethod
@abstractmethod
def mover(cls):
pass
@staticmethod
@abstractmethod
def respirar():
pass
class Cachorro(Animal):
@property
def som(self):
return "Au au"
@classmethod
def mover(cls):
return "Correndo"
@staticmethod
def respirar():
return "Inspirando e expirando"
cachorro = Cachorro()
print(cachorro.som) # Au au
print(Cachorro.mover()) # Correndo
print(Cachorro.respirar()) # Inspirando e expirando
Note que a ordem dos decoradores é importante: @property deve vir antes de @abstractmethod.
5. Métodos Concretos em Classes Abstratas
Classes abstratas não precisam conter apenas métodos abstratos. Elas podem incluir métodos concretos com implementação completa, que as subclasses podem herdar ou sobrescrever:
from abc import ABC, abstractmethod
class Veiculo(ABC):
def __init__(self, nome):
self.nome = nome
self.ligado = False
def ligar(self):
self.ligado = True
return f"{self.nome} ligado"
def desligar(self):
self.ligado = False
return f"{self.nome} desligado"
@abstractmethod
def acelerar(self):
pass
class Carro(Veiculo):
def acelerar(self):
if self.ligado:
return f"{self.nome} acelerando"
return f"Ligue {self.nome} primeiro"
carro = Carro("Fusca")
print(carro.ligar()) # Fusca ligado
print(carro.acelerar()) # Fusca acelerando
O método super() pode ser usado para chamar implementações da classe pai:
class CarroEletrico(Carro):
def ligar(self):
return super().ligar() + " silenciosamente"
eletrico = CarroEletrico("Tesla")
print(eletrico.ligar()) # Tesla ligado silenciosamente
6. Herança Múltipla com ABCs
Python suporta herança múltipla com classes abstratas, o que permite combinar funcionalidades de diferentes ABCs:
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def fazer_som(self):
pass
class Voador(ABC):
@abstractmethod
def voar(self):
pass
class Pato(Animal, Voador):
def fazer_som(self):
return "Quack!"
def voar(self):
return "Voando como um pato"
pato = Pato()
print(pato.fazer_som()) # Quack!
print(pato.voar()) # Voando como um pato
A Method Resolution Order (MRO) determina a ordem de busca de métodos em herança múltipla. Você pode verificá-la com ClassName.__mro__.
7. Registro de Subclasses Virtuais com register()
O método register() permite declarar que uma classe é subclasse de uma ABC sem que ela herde diretamente dela. Isso é útil para integração com classes de bibliotecas externas:
from abc import ABC, abstractmethod
class Iteravel(ABC):
@abstractmethod
def iterar(self):
pass
# Registrando list como subclasse virtual
Iteravel.register(list)
# Agora list é considerada subclasse de Iteravel
print(issubclass(list, Iteravel)) # True
print(isinstance([1, 2, 3], Iteravel)) # True
# Mas list não herda de Iteravel
print(list.__bases__) # (<class 'object'>,)
Isso permite que você use isinstance() e issubclass() para verificar conformidade com uma interface, mesmo que a classe não tenha sido projetada para isso.
8. Boas Práticas e Casos de Uso
ABCs são mais adequadas quando você precisa garantir que subclasses implementem uma interface específica. Em Python, no entanto, duck typing e protocolos (PEP 544) oferecem alternativas mais flexíveis.
Quando usar ABCs:
- Em bibliotecas e frameworks onde a consistência da interface é crítica
- Quando você precisa de herança múltipla com comportamento compartilhado
- Para criar hierarquias de classes com validação em tempo de instanciação
Quando evitar:
- Em código pequeno ou scripts simples
- Quando duck typing é suficiente
- Se você precisa de máxima flexibilidade (prefira protocolos)
O módulo collections.abc fornece diversas ABCs úteis como Sequence, Mapping, Iterable, que você pode usar para verificar interfaces de coleções. O módulo numbers também oferece ABCs para tipos numéricos.
Lembre-se: mantenha suas ABCs focadas e coesas. Uma classe abstrata deve representar um conceito claro e bem definido, com o mínimo de métodos abstratos necessários para estabelecer o contrato.
Referências
- Documentação oficial do módulo abc — Referência completa do módulo
abccom todos os decoradores e funcionalidades - PEP 3119 - Introducing Abstract Base Classes — Proposta original que introduziu ABCs em Python
- Python ABCs no Real Python — Tutorial prático com exemplos detalhados sobre classes abstratas
- collections.abc — Abstract Base Classes for Containers — ABCs prontas para uso em coleções e containers
- PEP 544 - Protocols: Structural subtyping — Alternativa moderna às ABCs usando protocolos e tipagem estrutural
- Python ABCs no GeeksforGeeks — Guia introdutório com exemplos variados de classes abstratas