O que "mro ()" faz?

125

Em django.utils.functional.py:

for t in type(res).mro():  # <----- this
    if t in self.__dispatch:
        return self.__dispatch[t][funcname](res, *args, **kw)

Eu não entendo mro(). O que faz e o que significa "mro"?

zjm1126
fonte

Respostas:

211

Acompanhe ...:

>>> class A(object): pass
... 
>>> A.__mro__
(<class '__main__.A'>, <type 'object'>)
>>> class B(A): pass
... 
>>> B.__mro__
(<class '__main__.B'>, <class '__main__.A'>, <type 'object'>)
>>> class C(A): pass
... 
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <type 'object'>)
>>> 

Desde que tenhamos uma herança única, __mro__é apenas a tupla de: a classe, sua base, sua base e assim por diante.object (funciona apenas para classes de novo estilo, é claro).

Agora, com herança múltipla ...:

>>> class D(B, C): pass
... 
>>> D.__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <type 'object'>)

... você também tem a garantia de que, em __mro__, nenhuma classe é duplicada e nenhuma classe vem após seus ancestrais, exceto que as classes que primeiro entram no mesmo nível de herança múltipla (como B e C neste exemplo) estão no __mro__esquerda para a direita.

Todos os atributos que você obtém na instância de uma classe, não apenas métodos, são conceitualmente pesquisados ​​ao longo de __mro__, portanto, se mais de uma classe entre os ancestrais definir esse nome, isso informa onde o atributo será encontrado - na primeira classe em o __mro__que define esse nome.

Alex Martelli
fonte
9
oi alex, existe alguma diferença entre D .__ mro__ e D.mro ().
zjm1126
26
mropode ser personalizado por uma metaclasse, é chamado uma vez na inicialização da classe e o resultado é armazenado em __mro__- consulte docs.python.org/library/… .
Alex Martelli
23
Por que é chamado de ordem de resolução do método em vez de ordem de resolução de atributo ?
Bentley4
1
Assim como @Alex Martelli disse e o conteúdo de python-history.blogspot.com/2010/06/… , o atributo mro deve ser adicionado quando a nova classe for usada, assim como somente quando Python 2.2 MRO e Python 2.3 MRO (C3) são usados.
andy
82

mro()significa Ordem de resolução de método. Ele retorna uma lista de tipos dos quais a classe é derivada, na ordem em que são pesquisados ​​por métodos.

mro () ou __mro__ funciona apenas em novas classes de estilo. No python 3, eles funcionam sem problemas. Mas no python 2 essas classes precisam herdar object.

Ned Batchelder
fonte
10

Talvez isso mostre a ordem da resolução.

class A(object):
    def dothis(self):
        print('I am from A class')

class B(A):
    pass

class C(object):
    def dothis(self):
        print('I am from C class')

class D(B, C):
    pass

d_instance= D()
d_instance.dothis()
print(D.mro())

e resposta seria

I am from A class
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.A'>, <class '__main__.C'>, <class 'object'>]

A regra é a profundidade em primeiro lugar, o que neste caso significaria D, B, A, C.

O Python normalmente usa uma ordem de profundidade ao pesquisar classes herdadas, mas quando duas classes são herdadas da mesma classe, o Python remove a primeira menção dessa classe do mro.

Stryker
fonte
1
Então, como você pode ver, a ordem é D, B, A, C
Stryker:
1
O que você quer dizer com "Python remove a primeira menção dessa classe do mro".
Ruthvik Vaila
2
@RuthvikVaila: Eu acho que essa parte está mal declarada. Nesta postagem do blog sobre a história do Python, Guido van Rossum comenta (sobre o esquema de MRO introduzido no Python 2.2) "Se alguma classe foi duplicada nesta pesquisa, todos, exceto a última ocorrência, seriam excluídos da lista de MRO" (grifo meu ) Isso implica que ele poderia remover mais do que apenas a primeira "menção" da classe.
martineau
1

A ordem da resolução será diferente na herança do diamante.

class A(object):
    def dothis(self):
        print('I am from A class')


class B1(A):
    def dothis(self):
        print('I am from B1 class')
    # pass


class B2(object):
    def dothis(self):
        print('I am from B2 class')
    # pass


class B3(A):
    def dothis(self):
        print('I am from B3 class')


# Diamond inheritance
class D1(B1, B3):
    pass


class D2(B1, B2):
    pass


d1_instance = D1()
d1_instance.dothis()
# I am from B1 class
print(D1.__mro__)
# (<class '__main__.D1'>, <class '__main__.B1'>, <class '__main__.B3'>, <class '__main__.A'>, <class 'object'>)


d2_instance = D2()
d2_instance.dothis()
# I am from B1 class
print(D2.__mro__)
# (<class '__main__.D2'>, <class '__main__.B1'>, <class '__main__.A'>, <class '__main__.B2'>, <class 'object'>)
Girish Gupta
fonte
mas por que a resolução é diferente?
Vadim
No cenário de herança múltipla, qualquer atributo especificado é pesquisado primeiro na classe atual. Se não for encontrada, a pesquisa continuará nas classes pai de maneira aprofundada, esquerda-direita, sem pesquisar duas vezes na mesma classe.
Girish Gupta
desculpe, mas eu não entendo por que, no primeiro caso, é ir, class B3mas no segundo caso, é ir paraclass A depoisclass B1
Vadim