Herança simples e sobrescrita de métodos
1. Fundamentos da Herança Simples
Herança é um dos pilares da programação orientada a objetos que permite que uma classe filha herde atributos e métodos de uma classe pai. Em Python, a sintaxe é direta: basta declarar a classe filha com o nome da classe pai entre parênteses.
class Animal:
def __init__(self, nome):
self.nome = nome
def mover(self):
return f"{self.nome} está se movendo"
class Cachorro(Animal):
def latir(self):
return f"{self.nome} está latindo"
# Uso
rex = Cachorro("Rex")
print(rex.mover()) # Herdou o método da classe pai
print(rex.latir()) # Método específico da filha
A classe Cachorro automaticamente tem acesso a todos os atributos e métodos de Animal. Isso promove reuso de código e estabelece uma relação hierárquica natural.
2. A Função super() e a Classe Base
A função super() é essencial para acessar métodos da classe pai dentro da classe filha. Ela é particularmente importante no construtor __init__, onde precisamos garantir que a inicialização da classe base ocorra corretamente.
class Animal:
def __init__(self, nome, idade):
self.nome = nome
self.idade = idade
class Cachorro(Animal):
def __init__(self, nome, idade, raca):
super().__init__(nome, idade) # Chama o __init__ da classe pai
self.raca = raca
def apresentar(self):
return f"Sou {self.nome}, tenho {self.idade} anos e sou da raça {self.raca}"
# Uso
bob = Cachorro("Bob", 3, "Labrador")
print(bob.apresentar())
Boas práticas: Sempre chame super().__init__() como primeira linha do construtor da classe filha, a menos que tenha um motivo muito específico para não fazê-lo. Isso garante que todos os atributos da classe base sejam inicializados corretamente.
3. Sobrescrita de Métodos (Method Overriding)
Sobrescrita de métodos permite que a classe filha redefina um método herdado, fornecendo uma implementação específica para seu contexto. A assinatura do método (nome e parâmetros) deve ser mantida, mas o comportamento interno pode ser completamente diferente.
class Animal:
def fazer_som(self):
return "Som genérico de animal"
class Cachorro(Animal):
def fazer_som(self):
return "Au au!"
class Gato(Animal):
def fazer_som(self):
return "Miau!"
# Polimorfismo em ação
animais = [Cachorro(), Gato(), Animal()]
for animal in animais:
print(animal.fazer_som())
Cada classe filha fornece sua própria implementação de fazer_som(), demonstrando como a sobrescrita permite comportamento polimórfico.
4. Controlando o Acesso com super() na Sobrescrita
A sobrescrita não precisa ser uma substituição total. Podemos usar super() para reutilizar a lógica do método pai e estendê-la com comportamento adicional.
class Veiculo:
def __init__(self, marca, modelo):
self.marca = marca
self.modelo = modelo
self.ligado = False
def ligar(self):
self.ligado = True
return f"{self.marca} {self.modelo} ligado"
class Carro(Veiculo):
def __init__(self, marca, modelo, portas):
super().__init__(marca, modelo) # Estende a inicialização
self.portas = portas
def ligar(self):
resultado = super().ligar() # Reutiliza a lógica do pai
return f"{resultado} com {self.portas} portas"
# Uso
fusca = Carro("VW", "Fusca", 2)
print(fusca.ligar())
A diferença fundamental: substituir totalmente significa ignorar completamente o método pai, enquanto estender usa super() para aproveitar parte do comportamento original.
5. A Ordem de Resolução de Métodos (MRO) em Herança Simples
Em herança simples (uma única classe pai), a MRO (Method Resolution Order) é linear e previsível: a classe filha, depois a classe pai, e assim por diante até object.
class A:
def metodo(self):
return "Método de A"
class B(A):
def metodo(self):
return "Método de B"
class C(B):
pass
# Visualizando a MRO
print(C.__mro__)
# Saída: (<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)
c = C()
print(c.metodo()) # Busca começa em C, vai para B, encontra o método
A MRO garante que o Python sempre encontre o método mais específico primeiro, seguindo a hierarquia de herança.
6. Herança e Atributos de Classe vs Instância
Atributos de classe são compartilhados entre todas as instâncias, incluindo as classes filhas. Isso pode levar a comportamentos inesperados se não for bem compreendido.
class Trabalhador:
ferias = 30 # Atributo de classe compartilhado
class Estagiario(Trabalhador):
ferias = 45 # Sobrescrita na classe filha
class Gerente(Trabalhador):
pass # Mantém o valor da classe pai
# Atributos de classe
print(Trabalhador.ferias) # 30
print(Estagiario.ferias) # 45
print(Gerente.ferias) # 30
# Cuidado com mutabilidade!
class Empresa:
funcionarios = [] # Lista compartilhada
class Filial(Empresa):
pass
Empresa.funcionarios.append("João")
Filial.funcionarios.append("Maria")
print(Empresa.funcionarios) # ['João', 'Maria'] - mesma lista!
Para evitar efeitos colaterais, prefira sempre inicializar atributos mutáveis no __init__ (atributos de instância), não como atributos de classe.
7. Boas Práticas e Armadilhas Comuns
Evite sobrescrita acidental: Tenha cuidado ao sobrescrever métodos especiais como __str__, __repr__ ou propriedades com @property.
class Produto:
def __init__(self, preco):
self._preco = preco
@property
def preco(self):
return self._preco
class ProdutoComDesconto(Produto):
@property
def preco(self): # Sobrescrita correta da property
return self._preco * 0.9
Quando preferir composição: Se a relação entre classes é "tem um" em vez de "é um", considere composição.
# Em vez de herança
class CarroEletrico(Veiculo): # "é um" veículo - OK
pass
# Composição pode ser melhor
class Motor:
def ligar(self):
return "Motor ligado"
class Carro:
def __init__(self):
self.motor = Motor() # "tem um" motor
Testando hierarquias: Use isinstance() e issubclass() para verificar relações.
class Animal: pass
class Cachorro(Animal): pass
rex = Cachorro()
print(isinstance(rex, Animal)) # True
print(isinstance(rex, Cachorro)) # True
print(issubclass(Cachorro, Animal)) # True
Herança simples é uma ferramenta poderosa quando usada corretamente. Lembre-se: modele hierarquias "é um" naturais, sempre chame super().__init__() em construtores, e prefira composição quando a relação não for claramente hierárquica.
Referências
- Python Official Documentation: Classes — Tutorial oficial sobre classes em Python, incluindo herança e sobrescrita de métodos
- Real Python: Inheritance and Composition — Guia prático sobre herança, composição e quando usar cada uma
- GeeksforGeeks: Method Overriding in Python — Exemplos detalhados de sobrescrita de métodos com casos de uso
- Programiz: Python super() Function — Explicação completa da função super() com exemplos práticos
- Stack Abuse: Method Resolution Order (MRO) in Python — Artigo técnico sobre MRO, C3 linearization e como o Python resolve métodos