web-dev-qa-db-fra.com

Ordre de résolution des méthodes (MRO) dans les classes de nouveau style?

Dans le livre Python in a Nutshell (2nd Edition) il y a un exemple qui utilise
classes de style ancien pour montrer comment les méthodes sont résolues dans l'ordre de résolution classique et
en quoi est-ce différent avec la nouvelle commande.

J'ai essayé le même exemple en réécrivant l'exemple dans un nouveau style mais le résultat n'est pas différent de celui obtenu avec les anciennes classes de style. La version python que j'utilise pour exécuter l'exemple est 2.5.2. Voici l'exemple:

class Base1(object):  
    def amethod(self): print "Base1"  

class Base2(Base1):  
    pass

class Base3(object):  
    def amethod(self): print "Base3"

class Derived(Base2,Base3):  
    pass

instance = Derived()  
instance.amethod()  
print Derived.__mro__  

L'appel instance.amethod() affiche Base1, mais selon ma compréhension du MRO avec un nouveau style de classes, la sortie aurait dû être Base3. L'appel Derived.__mro__ Imprime:

(<class '__main__.Derived'>, <class '__main__.Base2'>, <class '__main__.Base1'>, <class '__main__.Base3'>, <type 'object'>)

Je ne sais pas si ma compréhension de MRO avec de nouvelles classes de style est incorrecte ou que je fais une erreur stupide que je ne suis pas en mesure de détecter. Veuillez m'aider à mieux comprendre le MRO.

85
sateesh

La différence cruciale entre l'ordre de résolution pour les classes héritées et les classes de nouveau style survient lorsque la même classe ancêtre apparaît plus d'une fois dans l'approche "naïve", la profondeur d'abord - par exemple, considérons un cas "d'héritage diamant":

>>> class A: x = 'a'
... 
>>> class B(A): pass
... 
>>> class C(A): x = 'c'
... 
>>> class D(B, C): pass
... 
>>> D.x
'a'

ici, de style hérité, l'ordre de résolution est D - B - A - C - A: donc lorsque vous recherchez D.x, A est la première base de résolution pour le résoudre, masquant ainsi la définition en C. Tandis que:

>>> class A(object): x = 'a'
... 
>>> class B(A): pass
... 
>>> class C(A): x = 'c'
... 
>>> class D(B, C): pass
... 
>>> D.x
'c'
>>> 

ici, nouveau style, l'ordre est:

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

avec A forcé de venir dans l'ordre de résolution une seule fois et après toutes ses sous-classes, de sorte que les remplacements (c'est-à-dire le remplacement de C du membre x) fonctionnent réellement de manière sensée.

C'est l'une des raisons pour lesquelles les classes à l'ancienne devraient être évitées: l'héritage multiple avec des motifs "en forme de diamant" ne fonctionne tout simplement pas avec eux, alors qu'il le fait avec le nouveau style.

168
Alex Martelli

L'ordre de résolution des méthodes de Python est en fait plus complexe que la simple compréhension du motif en losanges. Pour vraiment le comprendre, jetez un œil à linéarisation C . J'ai trouvé qu'il est vraiment utile d'utiliser des instructions d'impression lors de l'extension des méthodes pour suivre la commande. Par exemple, quelle serait, selon vous, la sortie de ce modèle? (Remarque: le "X" est supposé être deux bords croisés, pas un nœud et ^ signifie des méthodes qui appellent super ())

class G():
    def m(self):
        print("G")

class F(G):
    def m(self):
        print("F")
        super().m()

class E(G):
    def m(self):
        print("E")
        super().m()

class D(G):
    def m(self):
        print("D")
        super().m()

class C(E):
    def m(self):
        print("C")
        super().m()

class B(D, E, F):
    def m(self):
        print("B")
        super().m()

class A(B, C):
    def m(self):
        print("A")
        super().m()


#      A^
#     / \
#    B^  C^
#   /| X
# D^ E^ F^
#  \ | /
#    G

Avez-vous obtenu A B D C E F G?

x = A()
x.m()

Après beaucoup d'essais et d'erreur, j'ai trouvé une interprétation informelle de la théorie des graphes de la linéarisation C3 comme suit: (Quelqu'un s'il vous plaît, faites-moi savoir si c'est faux.)

Considérez cet exemple:

class I(G):
    def m(self):
        print("I")
        super().m()

class H():
    def m(self):
        print("H")

class G(H):
    def m(self):
        print("G")
        super().m()

class F(H):
    def m(self):
        print("F")
        super().m()

class E(H):
    def m(self):
        print("E")
        super().m()

class D(F):
    def m(self):
        print("D")
        super().m()

class C(E, F, G):
    def m(self):
        print("C")
        super().m()

class B():
    def m(self):
        print("B")
        super().m()

class A(B, C, D):
    def m(self):
        print("A")
        super().m()

# Algorithm:

# 1. Build an inheritance graph such that the children point at the parents (you'll have to imagine the arrows are there) and
#    keeping the correct left to right order. (I've marked methods that call super with ^)

#          A^
#       /  |  \
#     /    |    \
#   B^     C^    D^  I^
#        / | \  /   /
#       /  |  X    /   
#      /   |/  \  /     
#    E^    F^   G^
#     \    |    /
#       \  |  / 
#          H
# (In this example, A is a child of B, so imagine an Edge going FROM A TO B)

# 2. Remove all classes that aren't eventually inherited by A

#          A^
#       /  |  \
#     /    |    \
#   B^     C^    D^
#        / | \  /  
#       /  |  X    
#      /   |/  \ 
#    E^    F^   G^
#     \    |    /
#       \  |  / 
#          H

# 3. For each level of the graph from bottom to top
#       For each node in the level from right to left
#           Remove all of the edges coming into the node except for the right-most one
#           Remove all of the edges going out of the node except for the left-most one

# Level {H}
#
#          A^
#       /  |  \
#     /    |    \
#   B^     C^    D^
#        / | \  /  
#       /  |  X    
#      /   |/  \ 
#    E^    F^   G^
#               |
#               |
#               H

# Level {G F E}
#
#         A^
#       / |  \
#     /   |    \
#   B^    C^   D^
#         | \ /  
#         |  X    
#         | | \
#         E^F^ G^
#              |
#              |
#              H

# Level {D C B}
#
#      A^
#     /| \
#    / |  \
#   B^ C^ D^
#      |  |  
#      |  |    
#      |  |  
#      E^ F^ G^
#            |
#            |
#            H

# Level {A}
#
#   A^
#   |
#   |
#   B^  C^  D^
#       |   |
#       |   |
#       |   |
#       E^  F^  G^
#               |
#               |
#               H

# The resolution order can now be determined by reading from top to bottom, left to right.  A B C E D F G H

x = A()
x.m()
21
Ben

Le résultat que vous obtenez est correct. Essayez de changer la classe de base de Base3 à Base1 et comparer avec la même hiérarchie pour les classes classiques:

class Base1(object):
    def amethod(self): print "Base1"

class Base2(Base1):
    pass

class Base3(Base1):
    def amethod(self): print "Base3"

class Derived(Base2,Base3):
    pass

instance = Derived()
instance.amethod()


class Base1:
    def amethod(self): print "Base1"

class Base2(Base1):
    pass

class Base3(Base1):
    def amethod(self): print "Base3"

class Derived(Base2,Base3):
    pass

instance = Derived()
instance.amethod()

Maintenant, il génère:

Base3
Base1

Lisez cette explication pour plus d'informations.

5
Denis Otkidach

Vous voyez ce comportement parce que la résolution de méthode est la profondeur d'abord, pas la largeur en premier. L'héritage de Dervied ressemble

         Base2 -> Base1
        /
Derived - Base3

Donc instance.amethod()

  1. Vérifie Base2, ne trouve pas de méthode.
  2. Constate que Base2 a hérité de Base1 et vérifie Base1. Base1 a un amethod, donc il est appelé.

Cela se reflète dans Derived.__mro__. Parcourez simplement Derived.__mro__ Et arrêtez lorsque vous trouvez la méthode recherchée.

0
jamessan