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, **kwargs se 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

Exercícios

  1. Crie uma classe base Animal com um método falar() que imprime "Som de animal". Crie duas classes Cachorro e Gato que herdam de Animal e sobrescrevem falar() para imprimir "Au au" e "Miau", respectivamente. Depois, crie uma classe CachorroGato que herda de Cachorro e Gato. 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
  2. Implemente um mixin LogMixin que adiciona um método log(self, mensagem) que imprime a mensagem com um timestamp. Use esse mixin em uma classe Usuario que possui atributos nome e email. Crie uma instância e chame o método log.

    ✓ 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
  3. 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.

  4. 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
  5. Explique por que mixins geralmente não possuem __init__ ou, se possuem, devem ser chamados com super().__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 chamar super().__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).