Pular para o conteúdo

    Capítulo 43, Avançado

    MRO, super cooperativo e mixins

    A herança múltipla assusta porque quase ninguém aprende a regra que a organiza. Ela é pequena: a linearização C3.

    Código deste capítulo: avancado/cap43_mro_mixins.py

    A ordem que o Python segue

    Cada classe tem uma lista ordenada de superclasses, o MRO. O algoritmo (C3) garante duas coisas: uma classe filha vem sempre antes das suas pais, e a ordem em que você listou as bases é respeitada. No problema do losango, a classe Base aparece uma única vez:

    avancado/cap43_mro_mixins.pylinhas 10 a 31
    class Base:
        def ola(self):
            return ["Base"]
    
    
    class Esquerda(Base):
        def ola(self):
            return ["Esquerda"] + super().ola()
    
    
    class Direita(Base):
        def ola(self):
            return ["Direita"] + super().ola()
    
    
    class Topo(Esquerda, Direita):
        def ola(self):
            return ["Topo"] + super().ola()
    
    
    print(Topo().ola())
    print([c.__name__ for c in Topo.__mro__])
    
    Saída
    ['Topo', 'Esquerda', 'Direita', 'Base']
    ['Topo', 'Esquerda', 'Direita', 'Base', 'object']
    

    O detalhe que quase todo mundo entende errado: super() não significa "a classe pai". Significa "a próxima classe no MRO do objeto". Dentro de Esquerda, o super().ola() chama a Direita, e não a Base. É isso que faz a cadeia funcionar com cooperação.

    __init__ cooperativo

    Para que todas as classes de uma hierarquia múltipla sejam inicializadas, cada uma deve consumir os argumentos que lhe pertencem e repassar o resto com super().__init__(**kwargs):

    avancado/cap43_mro_mixins.pylinhas 36 a 53
    class Nomeavel:
        def __init__(self, *, nome, **kwargs):
            super().__init__(**kwargs)
            self.nome = nome
    
    
    class Datavel:
        def __init__(self, *, data, **kwargs):
            super().__init__(**kwargs)
            self.data = data
    
    
    class Evento(Nomeavel, Datavel):
        pass
    
    
    e = Evento(nome="reunião", data="2026-10-06")
    print(e.nome, e.data)
    
    Saída
    reunião 2026-10-06
    

    Mixins

    Um mixin é uma classe pequena que oferece um comportamento para ser misturado em outras, sem ser uma entidade por si só. Ele não tem estado próprio nem __init__, e o nome costuma terminar em Mixin:

    avancado/cap43_mro_mixins.pylinhas 58 a 80
    import json
    
    
    class JsonMixin:
        def para_json(self):
            return json.dumps(vars(self), ensure_ascii=False)
    
    
    class ReprMixin:
        def __repr__(self):
            campos = ", ".join(f"{k}={v!r}" for k, v in vars(self).items())
            return f"{type(self).__name__}({campos})"
    
    
    class Usuario(JsonMixin, ReprMixin):
        def __init__(self, nome, idade):
            self.nome = nome
            self.idade = idade
    
    
    u = Usuario("Ana", 30)
    print(u)
    print(u.para_json())
    
    Saída
    Usuario(nome='Ana', idade=30)
    {"nome": "Ana", "idade": 30}
    

    Ganchos de subclasse

    O método __init_subclass__ roda sempre que alguém cria uma subclasse. É a ferramenta mais simples para registrar plugins, e na maioria dos casos substitui uma metaclasse:

    avancado/cap43_mro_mixins.pylinhas 85 a 101
    class Comando:
        comandos = {}
    
        def __init_subclass__(cls, nome=None, **kwargs):
            super().__init_subclass__(**kwargs)
            Comando.comandos[nome or cls.__name__.lower()] = cls
    
    
    class Listar(Comando, nome="ls"):
        pass
    
    
    class Remover(Comando):
        pass
    
    
    print(sorted(Comando.comandos))
    
    Saída
    ['ls', 'remover']
    

    Quando o MRO não fecha

    Se a ordem das bases contradiz a hierarquia, o Python recusa e diz por quê:

    avancado/cap43_mro_mixins.pylinhas 106 a 118
    class X:
        pass
    
    
    class Y(X):
        pass
    
    
    try:
        class Z(X, Y):
            pass
    except TypeError as erro:
        print(erro)
    
    Saída
    Cannot create a consistent method resolution
    order (MRO) for bases X, Y
    

    Minha regra para herança múltipla

    Eu uso herança múltipla em um caso: mixins sem estado, que oferecem um comportamento isolado. Para todo o resto, composição. Quando o super() cooperativo é necessário em uma hierarquia complexa, isso costuma ser o sinal de que o desenho pediria composição.

    Exercício 1

    Registro de comandos

    Usando __init_subclass__, faça Comando.comandos registrar Listar com o nome "ls" e Remover com o nome "remover", e verifique o resultado.