Python __mro__

joaovitorzv

Joao Vitor Veras

Posted on January 10, 2022

Python __mro__

Method Resolution Order ou (MRO) é um que atributo define a ordem em que classes são organizadas, para que os métodos sejam procurados e executados. Em linguagens que não permitem herdar múltiplas classes o MRO não é tão necessário, mas nas que permitem (como Python) ele tem um papel importante.

Quando herdamos uma classe a classe herdada pode ser chamada de Parent ou Superclass e a classe que esta herdando Child ou Subclass, o papel do MRO é definir a ordem das Superclasses em que, será procurado por (e caso encontrado, executado) algum método.

class Grandparent():
    def method(self):
        return 'grandparent'

class Parent(Grandparent):
    def method(self):
        return 'parent'

class Child(Parent):
    pass

child = Child()

print(child.method()) # > 'parent'
Enter fullscreen mode Exit fullscreen mode

um exemplo bem simples (sem fazer o uso de múltiplas heranças) é o código acima, temos 3 classes que possuem o mesmo método, o MRO primeiro procura pelo método na classe instanciada que estamos e caso não seja encontrado procura nas Superclasses, dessa forma: Child -> Parent -> Grandparent.

é possível visualizar essa ordem acessando o __mro__ na classe Child

child = Child()

# (<class '__main__.Child'>, <class '__main__.Parent'>, <class '__main__.Grandparent'>, <class 'object'>)
Enter fullscreen mode Exit fullscreen mode

python object class

podemos ver uma classe que não criamos no final <class 'object'>. essa é uma classe built-in do python, ela está presente para ser a Superclass de classes que não tenham explicitamente definido classes para herdar. Por isso a vimos no final do __mro__ pois a classe Grandparent não herda nenhuma outra classe.

agora utilizando múltiplas heranças vamos ver o MRO cumprindo o seu papel.

class Grandparent():
    def method(self):
        return 'grandparent'

class Parent(Grandparent):
    def method(self):
        return 'parent'

class Parent2(Grandparent):
    def method(self):
        return 'parent2'

class Child(Parent, Parent2):
    pass

child = Child()

print(child.method()) # > 'parent'
print(Child.__mro__)
# (<class '__main__.Child'>, <class '__main__.Parent'>, <class '__main__.Parent2'>, <class '__main__.Grandparent'>, <class 'object'>)
Enter fullscreen mode Exit fullscreen mode

como você pode na classe child temos o controle da ordem em que as Superclasses serão posicionadasChild(Parent, Parent2)

essa ordem fica disponível no atributo __bases__ de sua respectiva classe, por exemplo a classe Child acima:

print(Child.__bases__)
# (<class '__main__.Parent'>, <class '__main__.Parent2'>)

no exemplo do código acima já começa a ficar interessante, pois o esperado seria que o código tivesse o seguinte MRO

Child -> Parent -> Grandparent -> Parent2 -> Grandparent

mas isso não acontece porque como estamos lidando com herança a precedência importa, isso é, a classe Parent2 tem prioridade sobre a Grandparent, então o MRO define a seguinte ordem

Child -> Parent -> Parent2 -> GrandParent

então podemos concluir que Subclasses precedem Superclasses (como já dito ali em cima Subclasses, Superclasses = Child, Parent) e o nosso MRO se baseia na ordem do atributo __bases__

Exemplo 2

Se por acaso você tentar criar uma ordem que o MRO não consiga resolver você recebera um TypeError:

"TypeError: Cannot create a consistent method resolution
order (MRO) for bases"

Python Method Resolution Order inconsistente

no código acima não é possivel criar um Method Resolution Order consistente porque estamos tentando passar uma Superclass na frente de uma Subclass.

Ilustação Method Resolution Order inconsistente

um workaround para esse erro seria invertendo o __bases__ da classe Child Child(Parent3, Parent), por mais que funcione existe uma maneira melhor de resolver isso e que vai deixar o seu MRO mais organizado e bem definido e você não terá erros inesperados no futuro.

class Grandparent():
    def method(self):
        return 'grandparent'

class Parent():
    def method(self):
        return 'parent'

    def parent(self):
        return 'parent hierarquicamente'

class Parent2(Parent, Grandparent):
    def method(self):
        return 'parent2'

class Parent3(Parent2):
    pass

class Child(Parent3):
    pass

child = Child()

print(child.parent()) # parent hierarquicamente
Enter fullscreen mode Exit fullscreen mode

como temos a acesso a todos os métodos e atributos por hierarquia podemos remover a classe Parent da classe Child.

print(Child.__mro__)
# (<class '__main__.Child'>, <class '__main__.Parent3'>, <class '__main__.Parent2'>, <class '__main__.Parent'>, <class '__main__.Grandparent'>, <class 'object'>)
Enter fullscreen mode Exit fullscreen mode

Acima podemos ver como ficou o MRO, bem definido e não trara problemas no futuro.

💖 💪 🙅 🚩
joaovitorzv
Joao Vitor Veras

Posted on January 10, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related