Herança múltipla e mixins
Esta aula explora a herança múltipla em Python, incluindo o conceito de mixins e a ordem de resolução de métodos (MRO). São apresentados exemplos práticos, boas práticas e exercícios para fixação do conteúdo.
Herança múltipla é um recurso poderoso de Python que permite que uma classe derive de mais de uma classe base. Isso possibilita combinar comportamentos de diferentes fontes, mas também introduz complexidades como conflitos de nomes e a necessidade de entender a ordem de resolução de métodos (MRO). Nesta aula, vamos explorar herança múltipla, mixins, a MRO em detalhes e boas práticas para usar esses conceitos de forma eficaz.
Herança múltipla
Em Python, uma classe pode herdar de várias classes base, separadas por vírgulas na definição. Por exemplo: class C(A, B):. Isso significa que a classe C herda todos os atributos e métodos de A e B. Se houver conflito (mesmo nome de método em ambas as bases), a resolução segue a ordem das classes na definição (esquerda para direita) e depois a hierarquia.
Exemplo simples:
class A:
def metodo(self):
print("Método de A")
class B:
def metodo(self):
print("Método de B")
class C(A, B):
pass
c = C()
c.metodo() # Saída: Método de A
A classe C herda de A e B. Como A vem primeiro, o método de A é chamado.
Mixins
Mixins são classes projetadas para fornecer funcionalidades específicas que podem ser adicionadas a outras classes via herança múltipla. Eles não são destinados a serem instanciados sozinhos, mas sim para compor classes mais complexas. Um mixin geralmente implementa um conjunto de métodos que podem ser reutilizados em diferentes hierarquias.
Exemplo de mixin para serialização JSON:
class JsonMixin:
def to_json(self):
import json
return json.dumps(self.__dict__)
class Pessoa(JsonMixin):
def __init__(self, nome, idade):
self.nome = nome
self.idade = idade
p = Pessoa("João", 30)
print(p.to_json()) # {"nome": "João", "idade": 30}
Mixins são úteis para evitar duplicação de código e seguir o princípio de responsabilidade única.
MRO em profundidade
MRO (Method Resolution Order) é a ordem em que Python procura métodos em uma hierarquia de classes. Para herança múltipla, Python usa o algoritmo C3 Linearization, que garante que a ordem seja consistente e respeite a precedência das classes base. Você pode ver a MRO de uma classe usando o atributo __mro__ ou o método mro().
Exemplo:
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
A ordem é D, B, C, A, object. Isso satisfaz a regra de que B e C herdam de A, e D herda de B e C. Python garante que cada classe apareça antes de suas bases e que a ordem seja monotônica.
Entender a MRO é crucial para prever qual método será chamado quando há sobreposição. Use super() com cuidado para garantir que todos os métodos na MRO sejam chamados, especialmente em padrões como cooperação de mixins.
Boas práticas
Ao usar herança múltipla e mixins, siga estas recomendações:
- Use mixins para funcionalidades ortogonais: Mixins devem fornecer comportamentos independentes que não interfiram entre si.
- Evite herança múltipla profunda: Prefira composição quando a hierarquia se tornar complexa.
- Nomeie mixins com sufixo Mixin: Isso deixa claro o propósito da classe.
- Use super() corretamente: Em hierarquias com herança múltipla,
super()segue a MRO. Certifique-se de que todos os métodos aceitem argumentos compatíveis (usando*args, **kwargsse necessário). - Documente a ordem de herança: Explique por que as classes são listadas em determinada ordem para evitar confusão.
Exemplo de cooperação com super():
class A:
def metodo(self):
print("A")
class B(A):
def metodo(self):
print("B")
super().metodo()
class C(A):
def metodo(self):
print("C")
super().metodo()
class D(B, C):
def metodo(self):
print("D")
super().metodo()
d = D()
d.metodo()
# D
# B
# C
# A
A chamada super() em D segue a MRO e chama B, que chama C, que chama A. Isso permite que todos os métodos sejam executados.
Referências
- Documentação oficial: Herança múltipla
- Python MRO (Method Resolution Order)
- Real Python: Herança e composição
- GeeksforGeeks: Herança múltipla em Python
- Stack Overflow: Como super() funciona com herança múltipla
Exercícios
-
Crie uma classe base
Animalcom um métodofalar()que imprime "Som de animal". Crie duas classesCachorroeGatoque herdam deAnimale sobrescrevemfalar()para imprimir "Au au" e "Miau", respectivamente. Depois, crie uma classeCachorroGatoque herda deCachorroeGato. Qual método será chamado? Verifique a MRO.✓ Resposta:class Animal: def falar(self): print("Som de animal") class Cachorro(Animal): def falar(self): print("Au au") class Gato(Animal): def falar(self): print("Miau") class CachorroGato(Cachorro, Gato): pass print(CachorroGato.__mro__) # (<class 'CachorroGato'>, <class 'Cachorro'>, <class 'Gato'>, <class 'Animal'>, <class 'object'>) cg = CachorroGato() cg.falar() # Au au, pois Cachorro vem primeiro na MRO -
Implemente um mixin
LogMixinque adiciona um métodolog(self, mensagem)que imprime a mensagem com um timestamp. Use esse mixin em uma classeUsuarioque possui atributosnomeeemail. Crie uma instância e chame o métodolog.✓ Resposta:from datetime import datetime class LogMixin: def log(self, mensagem): timestamp = datetime.now().isoformat() print(f"[{timestamp}] {mensagem}") class Usuario(LogMixin): def __init__(self, nome, email): self.nome = nome self.email = email u = Usuario("Ana", "ana@email.com") u.log("Usuário criado") # Ex: [2025-03-28T10:30:00] Usuário criado -
Dada a seguinte hierarquia:
class A: pass,class B(A): pass,class C(A): pass,class D(B, C): pass. Explique a MRO de D e por que a ordem é D, B, C, A e não D, B, A, C.✓ Resposta:A MRO de D é D, B, C, A, object. O algoritmo C3 linearization garante que: 1) a precedência das classes base é respeitada (B vem antes de C porque D(B,C) lista B primeiro); 2) a monotonicidade é mantida: se B precede C na MRO de D, então em qualquer outra classe que herde de B e C, B deve vir antes de C. Além disso, A é superclasse de B e C, então aparece depois. A ordem D, B, A, C seria inválida porque C também herda de A, e A não pode aparecer antes de C se C é uma base direta de D? Na verdade, a ordem D, B, A, C violaria a regra de que C deve aparecer antes de suas superclasses (A). Portanto, a ordem correta é D, B, C, A.
-
Escreva um exemplo onde o uso de
super()em herança múltipla permite que métodos de várias classes sejam chamados em sequência. Use pelo menos três classes em uma hierarquia linear (diamante).✓ Resposta:class A: def acao(self): print("A") class B(A): def acao(self): print("B") super().acao() class C(A): def acao(self): print("C") super().acao() class D(B, C): def acao(self): print("D") super().acao() d = D() d.acao() # D # B # C # A -
Explique por que mixins geralmente não possuem
__init__ou, se possuem, devem ser chamados comsuper().__init__()de forma cooperativa. Dê um exemplo de um mixin com__init__que funciona corretamente em herança múltipla.✓ Resposta:Mixins geralmente não definem
__init__para evitar conflitos com as classes principais. Se definirem, devem chamarsuper().__init__()para garantir que todos os construtores na MRO sejam executados. Exemplo:class LogMixin: def __init__(self, *args, **kwargs): self.logs = [] super().__init__(*args, **kwargs) def log(self, msg): self.logs.append(msg) class Pessoa(LogMixin): def __init__(self, nome): self.nome = nome super().__init__() p = Pessoa("João") p.log("criado") print(p.logs) # ['criado']O
super().__init__()em Pessoa chama o__init__de LogMixin, que inicializa a lista de logs e chama o próximo na MRO (object).