Capítulo 28, Intermediário
Herança e polimorfismo
Herança reaproveita comportamento e polimorfismo permite tratar tipos diferentes do mesmo jeito. Eu mostro como usar, e principalmente quando preferir composição.
Código deste capítulo: intermediario/cap28_heranca.py
Herdar e sobrescrever
Uma classe filha recebe tudo da classe pai e pode sobrescrever o que quiser. O resultado é o polimorfismo: o mesmo código chama o mesmo nome de método e cada objeto responde do seu jeito:
class Animal:
def __init__(self, nome):
self.nome = nome
def falar(self):
return "..."
def apresentar(self):
return f"{self.nome} diz {self.falar()}"
class Cachorro(Animal):
def falar(self):
return "au"
class Gato(Animal):
def falar(self):
return "miau"
for animal in [Cachorro("Rex"), Gato("Mia"), Animal("Ser")]:
print(animal.apresentar())
Rex diz au
Mia diz miau
Ser diz ...
O apresentar está escrito uma vez e funciona para todos, porque chama self.falar(), e o self decide qual versão rodar.
super(): reaproveitar o pai
Quando a filha precisa estender o comportamento do pai, em vez de substituí-lo, ela chama o pai com super():
class Funcionario:
def __init__(self, nome, salario):
self.nome = nome
self.salario = salario
def bonus(self):
return self.salario * 0.1
class Gerente(Funcionario):
def __init__(self, nome, salario, equipe):
super().__init__(nome, salario)
self.equipe = equipe
def bonus(self):
return super().bonus() + 100 * len(self.equipe)
g = Gerente("Ana", 5000, ["Bia", "Caio"])
print(g.bonus())
700.0
Esquecer o super().__init__(...) é um erro clássico: o pai nunca inicializa os seus atributos, e o erro só aparece mais tarde.
Hierarquias e a ordem de busca
Herança pode ter vários níveis. A ordem em que o Python procura um método chama-se MRO (method resolution order) e pode ser consultada:
class A:
pass
class B(A):
pass
class C(B):
pass
print(issubclass(C, A), isinstance(C(), B))
print([k.__name__ for k in C.__mro__])
True True
['C', 'B', 'A', 'object']
A herança múltipla (class D(B, C)) existe e funciona, mas é fonte de complexidade. Eu a trato com mais detalhe no nível avançado.
Duck typing
Python não exige uma hierarquia para o polimorfismo funcionar. Se o objeto tem o método que você chama, ele serve: "se anda como pato e grasna como pato, é pato":
class Pato:
def falar(self):
return "quack"
class Robo:
def falar(self):
return "bip"
for coisa in (Pato(), Robo()):
print(coisa.falar())
quack
bip
Composição antes de herança
Herança significa "é um". Composição significa "tem um". Uma Carro é um veículo, mas tem um motor. Eu só herdo quando a relação "é um" é verdadeira e a filha pode ser usada em qualquer lugar onde o pai é esperado. Para todo o resto, componho:
class Motor:
def ligar(self):
return "motor ligado"
class Carro:
def __init__(self):
self.motor = Motor()
def partir(self):
return self.motor.ligar()
print(Carro().partir())
motor ligado
Por que eu prefiro composição
Herança acopla a filha aos detalhes internos do pai: mudar o pai pode quebrar todas as filhas. Composição acopla apenas à interface pública. Quando em dúvida, comece com composição. Dá para evoluir para herança depois, mas desfazer uma hierarquia é caro.
Exercício 1
Formas geométricas
Crie Forma com o método area() que levanta NotImplementedError, e as subclasses Retangulo(largura, altura) e Circulo(raio). Escreva area_total(formas).