Herança múltipla e mixins
Aula sobre herança múltipla e mixins em Python, abordando conceitos, exemplos práticos, MRO e boas práticas. O aluno aprenderá a usar múltiplas classes base e criar mixins reutilizáveis.
Nesta aula, exploraremos dois tópicos avançados de orientação a objetos em Python: herança múltipla e mixins. A herança múltipla permite que uma classe derive de mais de uma classe base, combinando comportamentos. Mixins são classes projetadas para fornecer funcionalidades específicas e reutilizáveis, sem a intenção de serem instanciadas sozinhas. Compreender esses conceitos e como o Python resolve conflitos de nomes através do MRO (Method Resolution Order) é fundamental para escrever código modular e eficiente.
Vamos começar com um exemplo simples para motivar o uso de herança múltipla, depois mergulharemos nos detalhes do MRO e finalizaremos com boas práticas para evitar armadilhas comuns.
Herança múltipla
Herança múltipla é a capacidade de uma classe herdar atributos e métodos de mais de uma classe pai. Em Python, isso é feito listando as classes base na definição da classe, separadas por vírgulas. Por exemplo, uma classe Gato pode herdar de Mamifero e AnimalDeEstimacao, combinando características de ambas.
Considere o seguinte exemplo:
class Mamifero:
def __init__(self, nome):
self.nome = nome
def amamentar(self):
print(f"{self.nome} está amamentando.")
class AnimalDeEstimacao:
def __init__(self, dono):
self.dono = dono
def brincar(self):
print(f"{self.nome} está brincando com {self.dono}.")
class Gato(Mamifero, AnimalDeEstimacao):
def __init__(self, nome, dono):
Mamifero.__init__(self, nome)
AnimalDeEstimacao.__init__(self, dono)
def miar(self):
print(f"{self.nome} diz: Miau!")
gato = Gato("Bichano", "Ana")
gato.amamentar()
gato.brincar()
gato.miar()Neste código, Gato herda de Mamifero e AnimalDeEstimacao. Observe que precisamos chamar explicitamente os construtores das classes base. Python não chama automaticamente os __init__ das classes pais em herança múltipla, a menos que usemos super() com cuidado. Isso nos leva ao próximo tópico: o MRO.
Mixins
Mixins são classes que fornecem funcionalidades específicas e são projetadas para serem combinadas com outras classes via herança múltipla. Elas não devem ser instanciadas sozinhas, mas sim usadas para adicionar comportamentos a outras classes. Por exemplo, podemos criar um mixin SerializavelMixin que adiciona métodos de serialização JSON a qualquer classe.
import json
class SerializavelMixin:
def para_json(self):
return json.dumps(self.__dict__)
class Pessoa:
def __init__(self, nome, idade):
self.nome = nome
self.idade = idade
class PessoaSerializavel(Pessoa, SerializavelMixin):
pass
p = PessoaSerializavel("João", 30)
print(p.para_json()) # {"nome": "João", "idade": 30}Mixins são úteis para reutilizar funcionalidades transversais, como logging, validação, ou formatação. Eles geralmente não possuem estado próprio (apenas métodos) e devem ser pequenos e focados. Uma boa prática é nomear mixins com o sufixo Mixin para indicar sua intenção.
MRO em profundidade
MRO (Method Resolution Order) é a ordem em que Python procura métodos e atributos em uma hierarquia de classes. Em herança múltipla, o MRO segue o algoritmo C3 Linearization, que garante uma ordem consistente e respeita a precedência das classes base. Podemos visualizar o MRO de uma classe usando o atributo especial __mro__ ou o método mro().
Exemplo:
class A:
def metodo(self):
print("A")
class B(A):
def metodo(self):
print("B")
class C(A):
def metodo(self):
print("C")
class D(B, C):
pass
print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
d = D()
d.metodo() # BO MRO de D é D -> B -> C -> A -> object. Quando chamamos d.metodo(), Python procura primeiro em D, depois B (encontra), e executa. Se B não tivesse o método, iria para C, depois A. O algoritmo C3 garante que as classes base sejam visitadas na ordem de declaração e que subclasses tenham precedência sobre superclasses.
Entender o MRO é crucial para usar super() corretamente. Em herança múltipla, super() segue o MRO, permitindo chamadas cooperativas. Exemplo:
class A:
def __init__(self):
print("A.__init__")
super().__init__()
class B:
def __init__(self):
print("B.__init__")
super().__init__()
class C(A, B):
def __init__(self):
print("C.__init__")
super().__init__()
c = C()
# C.__init__
# A.__init__
# B.__init__Note que super().__init__() em A chama B.__init__ porque o MRO de C é C -> A -> B -> object. Isso permite que todos os construtores sejam executados na ordem correta, desde que todas as classes usem super() de forma consistente.
Boas práticas
Ao usar herança múltipla e mixins, algumas boas práticas ajudam a evitar problemas:
- Prefira composição à herança quando possível. Herança múltipla pode tornar o código complexo e difícil de depurar.
- Use mixins para funcionalidades pequenas e específicas. Evite mixins que dependam de estado ou que exijam inicialização complexa.
- Nomeie mixins com o sufixo
Mixinpara deixar clara a intenção. - Evite diamantes problemáticos: quando duas classes base compartilham uma superclasse comum, o MRO resolve, mas pode causar comportamentos inesperados se não for bem compreendido.
- Sempre use
super()nos construtores para garantir que todos os__init__sejam chamados na ordem do MRO. - Documente o MRO esperado em classes com herança múltipla complexa.
Exemplo de um mixin bem projetado:
class LogavelMixin:
def log(self, mensagem):
print(f"[LOG] {self.__class__.__name__}: {mensagem}")
class Veiculo:
def __init__(self, modelo):
self.modelo = modelo
class Carro(Veiculo, LogavelMixin):
def __init__(self, modelo):
super().__init__(modelo)
self.log("Carro criado")
carro = Carro("Fusca")
# [LOG] Carro: Carro criadoReferências
- Python Documentation: Multiple Inheritance
- Python Glossary: MRO
- The Python 2.3 Method Resolution Order (C3 Linearization)
- Real Python: Multiple Inheritance in Python
- Stack Overflow: How does Python's super() work with multiple inheritance?
Exercícios
Crie uma classe
Animalcom um métodofalarque retorna uma string genérica. Crie duas classesCachorroeGatoque herdam deAnimale sobrescrevemfalarpara retornar "Au Au" e "Miau", respectivamente. Depois, crie uma classeCachorroGatoque herda deCachorroeGato. Qual será a saída deCachorroGato().falar()? Explique com base no MRO.✓ Resposta: A saída será "Au Au" porque o MRO de CachorroGato é: CachorroGato -> Cachorro -> Gato -> Animal -> object. Como Cachorro é a primeira classe base listada, seu métodofalaré encontrado primeiro. O MRO segue a ordem de declaração das classes base.Crie um mixin
AndarMixinque adiciona um métodoandarque imprime "Andando...". Crie uma classePessoaque herda deAndarMixine tem um atributonome. Instancie uma pessoa e chame o métodoandar.✓ Resposta:class AndarMixin: def andar(self): print("Andando...") class Pessoa(AndarMixin): def __init__(self, nome): self.nome = nome p = Pessoa("Maria") p.andar() # Andando...Explique o que é o algoritmo C3 Linearization e como ele determina o MRO em Python. Dê um exemplo com três classes onde o MRO não é trivial.
✓ Resposta: O C3 Linearization é um algoritmo que produz uma ordem linear das classes base, garantindo que subclasses precedam superclasses e que a ordem de declaração seja respeitada. Exemplo:class A: pass,class B(A): pass,class C(A): pass,class D(B, C): pass. O MRO de D é D -> B -> C -> A -> object. O algoritmo resolve o diamante garantindo que A seja visitado depois de B e C.Considere as classes:
class X: def metodo(self): print("X") class Y: def metodo(self): print("Y") class Z(X, Y): pass class W(Y, X): passQual é a saída de
Z().metodo()eW().metodo()? Explique.✓ Resposta:Z().metodo()imprime "X" porque o MRO de Z é Z -> X -> Y -> object.W().metodo()imprime "Y" porque o MRO de W é W -> Y -> X -> object. A ordem de declaração das classes base determina qual método é encontrado primeiro.Escreva um mixin
JSONMixinque adiciona um métodoto_jsonque retorna uma string JSON dos atributos da instância. Crie uma classeProdutocom atributosnomeeprecoque utiliza o mixin. Teste a serialização.✓ Resposta:import json class JSONMixin: def to_json(self): return json.dumps(self.__dict__) class Produto(JSONMixin): def __init__(self, nome, preco): self.nome = nome self.preco = preco p = Produto("Caneta", 1.50) print(p.to_json()) # {"nome": "Caneta", "preco": 1.5}