Classes abstratas (ABC)
Esta aula explica classes abstratas em Python usando o módulo ABC, cobrindo a definição de métodos abstratos, a criação de interfaces e uma visão geral dos protocolos (PEP 544). São apresentados exemplos práticos e boas práticas para design orientado a objetos.
Classes abstratas são um conceito fundamental da programação orientada a objetos que permite definir uma interface comum para um grupo de subclasses, garantindo que certos métodos sejam implementados. Em Python, isso é feito através do módulo abc (Abstract Base Classes). Nesta aula, vamos explorar como criar classes abstratas, declarar métodos abstratos, simular interfaces e conhecer os protocolos, uma alternativa moderna introduzida no Python 3.8.
Entender classes abstratas é essencial para projetar hierarquias de classes robustas, promovendo o reuso de código e a consistência. Diferente de linguagens como Java, Python não possui uma palavra-chave interface, mas o módulo abc oferece mecanismos equivalentes.
abc
O módulo abc fornece a metaclasse ABCMeta e o decorador abstractmethod para definir classes abstratas. Uma classe que herda de ABC (que usa ABCMeta) não pode ser instanciada diretamente se possuir métodos abstratos não implementados.
Exemplo básico de declaração de uma classe abstrata:
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def fazer_som(self):
pass
# Tentar instanciar Animal gera TypeError
# animal = Animal() # TypeError: Can't instantiate abstract class Animal with abstract methods fazer_som
class Cachorro(Animal):
def fazer_som(self):
return "Au au"
c = Cachorro()
print(c.fazer_som()) # Au auObserve que a classe Animal não pode ser instanciada porque possui um método abstrato. A subclasse Cachorro deve implementar fazer_som para ser concreta. Se não implementar, também será abstrata e não poderá ser instanciada.
Métodos abstratos
Métodos abstratos são declarados com o decorador @abstractmethod e não possuem implementação na classe base. Eles forçam as subclasses a fornecer uma implementação concreta. É possível ter métodos concretos (com implementação) em classes abstratas, que podem ser herdados ou sobrescritos.
Exemplo com método concreto:
from abc import ABC, abstractmethod
class Forma(ABC):
@abstractmethod
def area(self):
pass
def descricao(self):
return f"Sou uma forma com área {self.area()}"
class Quadrado(Forma):
def __init__(self, lado):
self.lado = lado
def area(self):
return self.lado ** 2
q = Quadrado(4)
print(q.descricao()) # Sou uma forma com área 16Note que descricao é um método concreto que chama o método abstrato area. Isso é permitido e muito útil. Além disso, você pode usar @abstractmethod em métodos estáticos, de classe e propriedades.
Interfaces em Python
Python não tem uma palavra-chave interface, mas podemos simular interfaces usando classes abstratas com todos os métodos abstratos (sem implementação). Uma interface define um contrato que as classes devem seguir. Diferente de herança múltipla, interfaces focam apenas no comportamento.
Exemplo de interface:
from abc import ABC, abstractmethod
class Imprimivel(ABC):
@abstractmethod
def imprimir(self):
pass
class Scanner(ABC):
@abstractmethod
def digitalizar(self):
pass
class Multifuncional(Imprimivel, Scanner):
def imprimir(self):
print("Imprimindo...")
def digitalizar(self):
print("Digitalizando...")
m = Multifuncional()
m.imprimir()
m.digitalizar()Note que a classe Multifuncional implementa duas interfaces. Isso mostra como Python lida com herança múltipla de classes abstratas. É importante que as interfaces tenham apenas métodos abstratos, sem estado, para evitar problemas com herança múltipla (diamante).
Protocolos (visão geral)
Protocolos foram introduzidos no Python 3.8 via PEP 544 como uma forma de duck typing estático. Eles permitem definir uma interface sem exigir herança, usando typing.Protocol. Uma classe que implementa os métodos definidos no protocolo é considerada compatível, mesmo sem herdar dele.
Exemplo de protocolo:
from typing import Protocol
class Voador(Protocol):
def voar(self) -> str:
...
class Passaro:
def voar(self) -> str:
return "Batendo asas"
class Aviao:
def voar(self) -> str:
return "Usando turbinas"
def fazer_voar(objeto: Voador):
print(objeto.voar())
fazer_voar(Passaro()) # Batendo asas
fazer_voar(Aviao()) # Usando turbinasProtocolos são úteis para tipagem estática e duck typing, sem acoplamento de herança. Diferente de ABCs, não há verificação em tempo de execução (a menos que use isinstance com runtime_checkable). Protocolos são mais leves e preferíveis quando você só precisa de uma interface sem hierarquia.
Boas práticas
Ao usar classes abstratas, prefira herança simples para evitar complexidade. Use interfaces (classes com todos métodos abstratos) para definir contratos. Para duck typing, considere usar protocolos. Sempre documente os métodos abstratos com docstrings claras. Evite criar classes abstratas com muitos métodos; prefira interfaces pequenas e focadas (princípio da segregação de interfaces).
Referências
- Documentação oficial do módulo abc
- Documentação de Protocol (typing)
- PEP 544 – Protocolos: tipagem estrutural (duck typing estático)
- Real Python: Python Interfaces
- GeeksforGeeks: Abstract Classes in Python
Exercícios
- Crie uma classe abstrata chamada
Veiculocom um método abstratomovere um método concretopararque imprime "Veículo parado". Depois implemente subclassesCarroeBicicleta. - Defina uma interface
Serializavelcom métodos abstratosserializaredesserializar. Implemente em uma classePessoaque serializa para JSON. - Crie um protocolo
Nadadorcom um métodonadarque retorna uma string. Implemente em classesPeixeeHumanosem herdar do protocolo. Escreva uma função que aceiteNadadore chamenadar. - Explique a diferença entre uma classe abstrata com métodos concretos e um protocolo. Dê um exemplo onde cada um é mais adequado.
- Crie uma classe abstrata
FormaGeometricacom um método abstratocalcular_areae um método concretoexibir_areaque imprime a área. ImplementeCirculoeRetangulo. Depois, crie uma lista de formas e exiba a área de cada uma.
from abc import ABC, abstractmethod
class Veiculo(ABC):
@abstractmethod
def mover(self):
pass
def parar(self):
print("Veículo parado")
class Carro(Veiculo):
def mover(self):
print("Carro se movendo")
class Bicicleta(Veiculo):
def mover(self):
print("Bicicleta se movendo")
c = Carro()
c.mover()
c.parar()
b = Bicicleta()
b.mover()
b.parar()
from abc import ABC, abstractmethod
import json
class Serializavel(ABC):
@abstractmethod
def serializar(self):
pass
@abstractmethod
def desserializar(self, dados):
pass
class Pessoa(Serializavel):
def __init__(self, nome, idade):
self.nome = nome
self.idade = idade
def serializar(self):
return json.dumps({"nome": self.nome, "idade": self.idade})
def desserializar(self, dados):
obj = json.loads(dados)
self.nome = obj["nome"]
self.idade = obj["idade"]
p = Pessoa("João", 30)
dados = p.serializar()
print(dados)
p2 = Pessoa("", 0)
p2.desserializar(dados)
print(p2.nome, p2.idade)
from typing import Protocol
class Nadador(Protocol):
def nadar(self) -> str:
...
class Peixe:
def nadar(self) -> str:
return "Nadando com barbatanas"
class Humano:
def nadar(self) -> str:
return "Nadando com braçadas"
def fazer_nadar(nadador: Nadador):
print(nadador.nadar())
fazer_nadar(Peixe())
fazer_nadar(Humano())
Classes abstratas podem ter implementação (métodos concretos) e estado (atributos), enquanto protocolos são puramente estruturais (só definem métodos, sem implementação). Classes abstratas são adequadas quando há uma hierarquia com código compartilhado (ex: classe base Animal com método concreto respirar). Protocolos são melhores para duck typing estático, quando não se quer acoplamento de herança (ex: interface Voador que pode ser implementada por classes não relacionadas).
from abc import ABC, abstractmethod
import math
class FormaGeometrica(ABC):
@abstractmethod
def calcular_area(self):
pass
def exibir_area(self):
print(f"Área: {self.calcular_area()}")
class Circulo(FormaGeometrica):
def __init__(self, raio):
self.raio = raio
def calcular_area(self):
return math.pi * self.raio ** 2
class Retangulo(FormaGeometrica):
def __init__(self, largura, altura):
self.largura = largura
self.altura = altura
def calcular_area(self):
return self.largura * self.altura
formas = [Circulo(3), Retangulo(4, 5)]
for forma in formas:
forma.exibir_area()