No artigo anterior criamos classes independentes. Mas e quando duas classes compartilham características comuns? Reescrever o mesmo código em cada uma viola um princípio fundamental do desenvolvimento de software: DRY — Don't Repeat Yourself. Herança resolve esse problema permitindo que uma classe herde atributos e métodos de outra, e polimorfismo permite que objetos de tipos diferentes respondam ao mesmo comando de formas distintas.
Herança Simples
Uma classe filha herda tudo da classe pai e pode estender ou modificar esse comportamento:
class Animal:
def __init__(self, nome, idade):
self.nome = nome
self.idade = idade
def apresentar(self):
return f"Eu sou {self.nome} e tenho {self.idade} ano(s)."
def emitir_som(self):
return "..."
class Cachorro(Animal):
def emitir_som(self):
return "Au au!"
def buscar(self, objeto):
return f"{self.nome} buscou o {objeto}!"
class Gato(Animal):
def emitir_som(self):
return "Miau!"
def arranhar(self):
return f"{self.nome} arranhou o sofá."
rex = Cachorro("Rex", 3)
mimi = Gato("Mimi", 5)
print(rex.apresentar()) # Eu sou Rex e tenho 3 ano(s).
print(rex.emitir_som()) # Au au!
print(rex.buscar("graveto")) # Rex buscou o graveto!
print(mimi.emitir_som()) # Miau!
Cachorro e Gato herdam apresentar() de Animal sem precisar reescrevê-lo. Cada uma sobrescreve emitir_som() com seu próprio comportamento — isso é sobrescrita de método (method overriding).
A Função super()
super() permite chamar métodos da classe pai a partir da classe filha — essencial para estender o construtor:
class Veiculo:
def __init__(self, marca, modelo, ano):
self.marca = marca
self.modelo = modelo
self.ano = ano
def descricao(self):
return f"{self.ano} {self.marca} {self.modelo}"
class Carro(Veiculo):
def __init__(self, marca, modelo, ano, portas):
super().__init__(marca, modelo, ano) # chama __init__ do pai
self.portas = portas
def descricao(self):
base = super().descricao() # reutiliza o método do pai
return f"{base} ({self.portas} portas)"
class CarroEletrico(Carro):
def __init__(self, marca, modelo, ano, portas, autonomia_km):
super().__init__(marca, modelo, ano, portas)
self.autonomia_km = autonomia_km
def descricao(self):
base = super().descricao()
return f"{base} — Elétrico, {self.autonomia_km}km de autonomia"
civic = Carro("Honda", "Civic", 2023, 4)
model3 = CarroEletrico("Tesla", "Model 3", 2024, 4, 560)
print(civic.descricao())
# 2023 Honda Civic (4 portas)
print(model3.descricao())
# 2024 Tesla Model 3 (4 portas) — Elétrico, 560km de autonomia
Polimorfismo
Polimorfismo significa "muitas formas". Em Python, objetos de classes diferentes podem ser tratados de forma uniforme se implementarem os mesmos métodos:
class Circulo:
def __init__(self, raio):
self.raio = raio
def area(self):
import math
return math.pi * self.raio ** 2
def perimetro(self):
import math
return 2 * math.pi * self.raio
class Retangulo:
def __init__(self, largura, altura):
self.largura = largura
self.altura = altura
def area(self):
return self.largura * self.altura
def perimetro(self):
return 2 * (self.largura + self.altura)
class Triangulo:
def __init__(self, base, altura, lado_a, lado_b, lado_c):
self.base = base
self.altura = altura
self.lados = (lado_a, lado_b, lado_c)
def area(self):
return (self.base * self.altura) / 2
def perimetro(self):
return sum(self.lados)
# Polimorfismo em ação — mesma interface, comportamentos diferentes
formas = [
Circulo(5),
Retangulo(4, 6),
Triangulo(3, 4, 3, 4, 5),
]
for forma in formas:
nome = type(forma).__name__
print(f"{nome:12} | Área: {forma.area():7.2f} | Perímetro: {forma.perimetro():.2f}")
Saída:
Circulo | Área: 78.54 | Perímetro: 31.42
Retangulo | Área: 24.00 | Perímetro: 20.00
Triangulo | Área: 6.00 | Perímetro: 12.00
Nenhuma das classes herda da outra — mesmo assim podemos tratá-las uniformemente. Isso é o duck typing do Python: se o objeto tem o método area(), ele pode ser usado onde area() for chamado. "Se anda como pato e grasna como pato, é um pato."
Classes Abstratas
Quando você quer garantir que subclasses implementem determinados métodos, use o módulo abc:
from abc import ABC, abstractmethod
class Forma(ABC):
"""Classe base abstrata — não pode ser instanciada diretamente."""
@abstractmethod
def area(self):
"""Toda forma deve implementar area()."""
pass
@abstractmethod
def perimetro(self):
"""Toda forma deve implementar perimetro()."""
pass
def descricao(self):
"""Método concreto — herdado por todas as subclasses."""
return (f"{type(self).__name__}: "
f"área={self.area():.2f}, "
f"perímetro={self.perimetro():.2f}")
class Quadrado(Forma):
def __init__(self, lado):
self.lado = lado
def area(self):
return self.lado ** 2
def perimetro(self):
return 4 * self.lado
class Hexagono(Forma):
def __init__(self, lado):
self.lado = lado
def area(self):
import math
return (3 * math.sqrt(3) / 2) * self.lado ** 2
def perimetro(self):
return 6 * self.lado
# Forma() # TypeError — não pode instanciar classe abstrata
q = Quadrado(5)
h = Hexagono(4)
print(q.descricao()) # Quadrado: área=25.00, perímetro=20.00
print(h.descricao()) # Hexagono: área=41.57, perímetro=24.00
Herança Múltipla e MRO
Python suporta herança múltipla — uma classe pode herdar de várias classes ao mesmo tempo:
class Nadador:
def nadar(self):
return f"{self.nome} está nadando."
class Corredor:
def correr(self):
return f"{self.nome} está correndo."
class Ciclista:
def pedalar(self):
return f"{self.nome} está pedalando."
class Triatleta(Nadador, Corredor, Ciclista):
def __init__(self, nome):
self.nome = nome
def competir(self):
return [self.nadar(), self.pedalar(), self.correr()]
atleta = Triatleta("Carlos")
for atividade in atleta.competir():
print(atividade)
# Carlos está nadando.
# Carlos está pedalando.
# Carlos está correndo.
Quando há ambiguidade sobre qual método usar, Python segue o MRO — Method Resolution Order, calculado pelo algoritmo C3:
print(Triatleta.__mro__)
# (<class 'Triatleta'>, <class 'Nadador'>, <class 'Corredor'>,
# <class 'Ciclista'>, <class 'object'>)
Verificando Herança
rex = Cachorro("Rex", 3)
print(isinstance(rex, Cachorro)) # True
print(isinstance(rex, Animal)) # True — herança
print(isinstance(rex, Gato)) # False
print(issubclass(Cachorro, Animal)) # True
print(issubclass(Gato, Cachorro)) # False
Exemplo Completo: Sistema de Pagamentos
from abc import ABC, abstractmethod
class MetodoPagamento(ABC):
def __init__(self, titular):
self.titular = titular
@abstractmethod
def processar(self, valor):
pass
@abstractmethod
def descricao(self):
pass
def recibo(self, valor):
return (f"Recibo — {self.titular}\n"
f"Método: {self.descricao()}\n"
f"Valor: R${valor:.2f}\n"
f"Status: {self.processar(valor)}")
class CartaoCredito(MetodoPagamento):
def __init__(self, titular, numero, limite):
super().__init__(titular)
self.numero = f"**** **** **** {numero[-4:]}"
self.limite = limite
self._gasto = 0
def processar(self, valor):
if self._gasto + valor > self.limite:
return "RECUSADO — limite insuficiente"
self._gasto += valor
return "APROVADO"
def descricao(self):
return f"Cartão de Crédito {self.numero}"
class Pix(MetodoPagamento):
def __init__(self, titular, chave):
super().__init__(titular)
self.chave = chave
def processar(self, valor):
return "APROVADO — transferência instantânea"
def descricao(self):
return f"Pix ({self.chave})"
class Boleto(MetodoPagamento):
def __init__(self, titular):
super().__init__(titular)
def processar(self, valor):
return "PENDENTE — aguardando compensação"
def descricao(self):
return "Boleto Bancário"
pagamentos = [
CartaoCredito("Ana", "1234567890123456", 2000),
Pix("Bruno", "bruno@email.com"),
Boleto("Carla"),
]
valores = [150.00, 89.90, 320.00]
for metodo, valor in zip(pagamentos, valores):
print(metodo.recibo(valor))
print("-" * 40)
Resumo
- Herança permite que uma classe filha reutilize código da classe pai
super()acessa métodos e o construtor da classe pai sem acoplamento direto- Polimorfismo trata objetos de tipos diferentes de forma uniforme pela mesma interface
- Duck typing: Python não exige herança formal — basta o objeto ter os métodos esperados
- Classes abstratas com
ABCe@abstractmethodgarantem que subclasses implementem contratos - Herança múltipla é suportada; o MRO define a ordem de resolução de métodos
isinstance()verifica instância considerando herança;issubclass()verifica hierarquia de classes
Referências e Leituras Complementares
- Herança — documentação oficial — https://docs.python.org/3/tutorial/classes.html#inheritance
- Módulo abc — classes abstratas — https://docs.python.org/3/library/abc.html
- MRO e algoritmo C3 — https://docs.python.org/3/howto/mro.html
- Duck typing e protocolos — https://docs.python.org/3/glossary.html#term-duck-typing
- RAMALHO, Luciano. Fluent Python. 2. ed. O'Reilly Media, 2022. Cap. 14 — herança, para o bem e para o mal.
- PHILLIPS, Dusty; LOTT, Steven F. Python Object-Oriented Programming. 4. ed. Packt, 2021. Cap. 4–5 — herança e polimorfismo com exemplos avançados.
- GAMMA, Erich et al. Design Patterns. Addison-Wesley, 1994. — a base teórica por trás do uso de herança e polimorfismo em sistemas reais.