111 votes

L'ordre de résolution des méthodes (ORM) dans les classes de type nouveau ?

Dans le livre Python en quelques mots (2ème édition) Il existe un exemple qui utilise
les anciennes classes pour montrer comment les méthodes sont résolues dans l'ordre de résolution classique et
en quoi la situation est-elle différente avec le nouvel ordre.

J'ai essayé le même exemple en le réécrivant dans le nouveau style, mais le résultat n'est pas différent de celui obtenu avec les classes de l'ancien style. La version de python que j'utilise pour exécuter l'exemple est la suivante 2.5.2. Voici un 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() empreintes Base1 Mais d'après ce que j'ai compris du MRO avec le nouveau style de classes, le résultat aurait dû être le suivant Base3 . L'appel Derived.__mro__ empreintes :

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

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

204voto

Alex Martelli Points 330805

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

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

ici, dans le style héritage, l'ordre de résolution est D - B - A - C - A : ainsi, lorsque l'on cherche D.x, A est la première base dans l'ordre de résolution pour le résoudre, cachant ainsi la définition dans 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 contraint de n'entrer dans l'ordre de résolution qu'une seule fois et après toutes ses sous-classes, de sorte que les surcharges (c'est-à-dire la surcharge par C du membre x ) fonctionnent de manière raisonnable.

C'est l'une des raisons pour lesquelles les classes de type ancien doivent être évitées : l'héritage multiple avec des modèles "en diamant" ne fonctionne pas de manière sensée avec elles, alors que c'est le cas avec les classes de type nouveau.

28voto

Ben Gorman Points 1075

L'ordre de résolution de la méthode Python est en fait plus complexe que la simple compréhension du motif en diamant. Pour vraiment pour le comprendre, consultez le site Linéarisation C3 . J'ai constaté qu'il était très utile d'utiliser des instructions d'impression lorsque l'on étend les méthodes pour suivre l'ordre. Par exemple, quel serait, selon vous, le résultat de ce modèle ? (Note : le 'X' est supposé être deux arêtes qui se croisent, pas un noeud et ^ signifie les 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 de nombreux essais et erreurs, j'ai trouvé une interprétation informelle de la linéarisation C3 dans le cadre de la théorie des graphes, comme suit : (Que quelqu'un me dise si c'est faux.)

Prenons l'exemple suivant :

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()

5voto

Denis Otkidach Points 13111

Le résultat obtenu est correct. Essayez de changer la classe de base de Base3 a 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 sort :

Base3
Base1

Lire cette explication pour plus d'informations.

1voto

jamessan Points 16420

Ce comportement s'explique par le fait que la résolution des méthodes se fait en profondeur et non en largeur. 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 Il est donc appelé.

Cela se traduit par Derived.__mro__ . Il suffit d'itérer sur Derived.__mro__ et arrêtez-vous lorsque vous avez trouvé la méthode recherchée.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X