Herança múltipla e MRO

1. Introdução à herança múltipla em Python

Herança múltipla é um recurso poderoso da programação orientada a objetos que permite que uma classe filha herde atributos e métodos de duas ou mais classes pai. Em Python, essa funcionalidade é implementada de forma nativa e elegante, diferentemente de linguagens como Java que não a suportam diretamente.

A sintaxe básica é simples: basta listar as classes pai separadas por vírgula na definição da classe filha.

class Mamifero:
    def som(self):
        return "Som de mamífero"

class Voador:
    def som(self):
        return "Som de voador"

class Morcego(Mamifero, Voador):
    def __init__(self, nome):
        self.nome = nome

batman = Morcego("Batman")
print(batman.som())  # Qual método será chamado?

Neste exemplo, Morcego herda de Mamifero e Voador, ambas com um método som(). A pergunta crucial é: qual delas será executada? A resposta está no MRO.

2. O problema do diamante

O "problema do diamante" é um desafio clássico da herança múltipla. Ele ocorre quando uma classe herda de duas classes que, por sua vez, herdam de uma mesma classe ancestral.

class A:
    def metodo(self):
        print("Método de A")

class B(A):
    def metodo(self):
        print("Método de B")

class C(A):
    def metodo(self):
        print("Método de C")

class D(B, C):
    pass

d = D()
d.metodo()  # Qual método será chamado?

Em linguagens como C++, isso pode gerar ambiguidades que exigem soluções complexas como herança virtual. Python, no entanto, resolve esse problema de forma transparente através do MRO, utilizando o algoritmo C3 Linearization.

3. MRO (Method Resolution Order) – ordem de resolução de métodos

O MRO é o mecanismo que determina a ordem em que as classes são pesquisadas quando um método é chamado em um objeto. Python utiliza o algoritmo C3 Linearization, que garante três propriedades:

  1. Subclasses vêm antes das superclasses
  2. A ordem de declaração das classes pai é preservada
  3. A hierarquia é consistente e monotônica

Para inspecionar o MRO de uma classe, podemos usar o atributo __mro__ ou o método mro():

print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

A ordem segue da classe atual até object, passando pelas classes pai na ordem declarada, respeitando a hierarquia.

4. Visualizando e entendendo o MRO na prática

Vamos criar um exemplo mais complexo para entender como o MRO funciona:

class Animal:
    def __init__(self):
        print("Animal init")

class Mamifero(Animal):
    def __init__(self):
        print("Mamifero init")
        super().__init__()

class Ave(Animal):
    def __init__(self):
        print("Ave init")
        super().__init__()

class Ornitorrinco(Mamifero, Ave):
    def __init__(self):
        print("Ornitorrinco init")
        super().__init__()

print(Ornitorrinco.__mro__)
# (<class '__main__.Ornitorrinco'>, <class '__main__.Mamifero'>, 
#  <class '__main__.Ave'>, <class '__main__.Animal'>, <class 'object'>)

perry = Ornitorrinco()
# Ornitorrinco init
# Mamifero init
# Ave init
# Animal init

Observe como super().__init__() segue o MRO, chamando os métodos __init__ na ordem correta: OrnitorrincoMamiferoAveAnimalobject.

5. Trabalhando com super() em herança múltipla

O super() em Python não chama simplesmente a classe pai imediata, mas sim o próximo na cadeia do MRO. Isso permite uma cooperação coordenada entre as classes na hierarquia.

class Base:
    def __init__(self, **kwargs):
        print(f"Base init: {kwargs}")
        self.base_attr = kwargs.get('base_attr')

class MixinA:
    def __init__(self, **kwargs):
        print(f"MixinA init: {kwargs}")
        self.a_attr = kwargs.get('a_attr')
        super().__init__(**kwargs)

class MixinB:
    def __init__(self, **kwargs):
        print(f"MixinB init: {kwargs}")
        self.b_attr = kwargs.get('b_attr')
        super().__init__(**kwargs)

class Concreta(MixinA, MixinB, Base):
    def __init__(self, **kwargs):
        print(f"Concreta init: {kwargs}")
        super().__init__(**kwargs)

obj = Concreta(base_attr=1, a_attr=2, b_attr=3)
print(Concreta.__mro__)

Cuidado: Ao usar super() em cadeia, as assinaturas dos métodos devem ser compatíveis. O uso de **kwargs é uma técnica comum para contornar esse problema.

6. Boas práticas e armadilhas comuns

Armadilhas:

  1. Inicialização inconsistente: Se uma classe na hierarquia não chamar super().__init__(), a cadeia é quebrada
  2. Assinaturas incompatíveis: Métodos com parâmetros diferentes podem causar erros
  3. Complexidade excessiva: Herança múltipla profunda torna o código difícil de entender

Boas práticas:

  • Prefira composição sobre herança múltipla sempre que possível
  • Use herança múltipla principalmente para mixins
  • Mantenha a hierarquia rasa (no máximo 2-3 níveis)
  • Documente o MRO esperado para classes complexas

7. Mixins: aplicação prática da herança múltipla

Mixins são classes pequenas e focadas que adicionam funcionalidades específicas. Eles são uma das aplicações mais elegantes da herança múltipla em Python.

class JSONMixin:
    def to_json(self):
        import json
        return json.dumps(self.__dict__)

class LogMixin:
    def log(self, mensagem):
        print(f"[LOG] {self.__class__.__name__}: {mensagem}")

class SerializavelMixin:
    def serialize(self):
        return str(self.__dict__)

class Usuario(JSONMixin, LogMixin, SerializavelMixin):
    def __init__(self, nome, email):
        self.nome = nome
        self.email = email
        self.log("Usuário criado")

class Produto(JSONMixin, LogMixin):
    def __init__(self, nome, preco):
        self.nome = nome
        self.preco = preco
        self.log("Produto criado")

user = Usuario("João", "joao@email.com")
print(user.to_json())
print(user.serialize())

prod = Produto("Notebook", 3500.00)
print(prod.to_json())

Mixins resolvem o problema do diamante de forma limpa porque são classes pequenas e independentes, sem hierarquia complexa entre si.

8. Conclusão e comparação com outros temas da série

O MRO é a solução elegante do Python para o desafio da herança múltipla. Diferentemente de linguagens como C++, que exigem herança virtual para resolver o problema do diamante, Python oferece uma abordagem transparente e previsível através do algoritmo C3 Linearization.

Enquanto a herança simples (tema vizinho desta série) estabelece uma hierarquia linear clara, a herança múltipla permite combinar comportamentos de forma flexível. O MRO trabalha em conjunto com o polimorfismo, resolvendo dinamicamente qual método executar com base na ordem linearizada.

Recomendação final: Use herança múltipla com moderação. Sempre verifique o MRO com Classe.__mro__ ao trabalhar com hierarquias complexas. Prefira mixins para adicionar funcionalidades transversais e considere composição como alternativa mais simples e testável.

Referências