Порядок разрешения методов (MRO) в классах нового стиля?

StackOverflow https://stackoverflow.com/questions/1848474

  •  13-09-2019
  •  | 
  •  

Вопрос

В книге Python в двух словах (2-е издание) есть пример, в котором используется
классы старого стиля, демонстрирующие, как методы разрешаются в классическом порядке разрешения и
чем это отличается от нового порядка?

Я попробовал тот же пример, переписав пример в новом стиле, но результат ничем не отличается от того, что был получен с помощью классов старого стиля.Версия python, которую я использую для запуска примера, является 2.5.2. Ниже приведен пример:

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__  

Призыв instance.amethod() С принтами Base1, но , согласно моему пониманию MRO с новым стилем классов, результат должен был быть Base3.Призыв Derived.__mro__ С принтами:

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

Я не уверен, что мое понимание MRO с новыми классами стилей неверно или что я делаю глупую ошибку, которую я не в состоянии обнаружить.Пожалуйста, помогите мне лучше понять MRO.

Это было полезно?

Решение

Решающее различие между порядком разрешения для устаревших классов и классов нового стиля возникает, когда один и тот же класс-предок встречается более одного раза в "наивном" подходе, ориентированном на глубину - например, рассмотрим случай "алмазного наследования":

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

здесь, в устаревшем стиле, порядок разрешения - D - B - A - C - A :таким образом, при поиске D.x A является первой базой для разрешения, чтобы решить ее, тем самым скрывая определение в C.В то время как:

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

здесь, в новом стиле, порядок такой:

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

с A принудительно выполняется в порядке разрешения только один раз и после всех его подклассов, так что переопределяет (т. Е. Переопределяет C для member x) на самом деле работают разумно.

Это одна из причин, по которой следует избегать занятий в старом стиле:множественное наследование с "ромбовидными" шаблонами просто не работает с ними разумно, в то время как с новым стилем это работает.

Другие советы

Порядок разрешения метода Python на самом деле сложнее, чем просто понимание ромбовидного рисунка.Для действительно поймите это, взгляните на Линеаризация C3.Я обнаружил, что использование инструкций print действительно помогает при расширении методов отслеживания заказа.Например, как вы думаете, каким будет результат этого шаблона?(Примечание:предполагается, что 'X' - это два пересекающихся ребра, а не узел, и ^ обозначает методы, которые вызывают 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

Ты получил A B D C E F G?

x = A()
x.m()

После долгих проб и ошибок я пришел к неформальной интерпретации линеаризации C3 в теории графов следующим образом:(Кто-нибудь, пожалуйста, дайте мне знать, если это неправильно.)

Рассмотрим этот пример:

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

Результат, который вы получаете, является правильным.Попробуйте изменить базовый класс Base3 Для Base1 и сравните с той же иерархией для классических классов:

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

Теперь он выводит:

Base3
Base1

Читать это объяснение для получения дополнительной информации.

Вы видите такое поведение, потому что разрешение метода зависит в первую очередь от глубины, а не от ширины.Наследство Дервиеда выглядит следующим образом

         Base2 -> Base1
        /
Derived - Base3

Итак instance.amethod()

  1. Проверяет Base2, не находит amethod.
  2. Видит, что Base2 унаследован от Base1, и проверяет Base1.Base1 имеет amethod, так это называется.

Это отражено в Derived.__mro__.Просто повторите Derived.__mro__ и остановитесь, когда вы найдете искомый метод.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top