2009-12-04 4 views
71

В книге Python в двух словах (второе издание) есть пример, который использует
старых классов стилей, чтобы продемонстрировать, как методы будут решены в классическом порядке разрешения и
, как он отличается с новым порядком.Метод разрешения разрешения (MRO) в классах нового стиля?

Я попробовал такой же пример, переписав пример в новом стиле, но результат не отличается от того, что было получено со старыми классами стилей. Версия 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.

ответ

144

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

>>> 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: так при поиске Dx, 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 вынужден прийти в порядке разрешения только один раз, и после того, как все его подклассы, так что переопределение (т.е. Кассиопеяне переопределение члена x) фактически работаем разумно.

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

+1

«[класс предков] [[]] вынужден прийти в порядке разрешения только один раз и после всех его подклассов, так что переопределения (т. Е. Переопределение C элемента x) действительно работают разумно». - * Epiphany! * Благодаря этому предложению я снова могу сделать MRO в своей голове. \ o/Большое спасибо. – Esteis

5

Результат, который вы получаете, является правильным. Попробуйте изменить базовый класс 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 

Read this explanation для получения дополнительной информации.

1

Вы видите это поведение, потому что разрешение метода является глубиной, а не шириной. Наследование Dervied выглядит как

  Base2 -> Base1 
     /
Derived - Base3 

Так instance.amethod()

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

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

+0

Я сомневаюсь, что причина, по которой я получаю «Base1» в качестве ответа, заключается в том, что разрешение метода является глубиной во-первых, я думаю, что для него есть нечто большее, чем подход, основанный на глубине. См. Пример Дениса, если это была глубина, сначала o/p должна была быть «Base1». Также обратитесь к первому примеру в предоставленной вами ссылке, также показанный MRO указывает, что разрешение метода определяется не только путем перемещения по глубине-первого порядка. – sateesh

+0

Извините, что ссылка на документ на MRO предоставлена ​​Денисом. Пожалуйста, проверьте это, я ошибся, что вы предоставили мне ссылку на python.org. – sateesh

+4

Обычно это глубина, но есть умники для обработки алмазного наследования, как объяснил Алекс. – jamessan

14

Порядок решения метода Python на самом деле более сложный, чем просто понимание шаблона бриллианта. До действительно поймите это, посмотрите на C3 linearization. Я нашел, что это действительно помогает использовать заявления печати при расширении методов для отслеживания заказа. Например, что, по вашему мнению, будет результатом этого шаблона? (Примечание: 'X', предполагают, чтобы быть два пересечения ребра, а не узел, а^означает методы, которые вызывают супер())

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

Вы должны исправить свой второй код: вы поместили класс «I» в первую строку, а также использовали супер, чтобы найти суперкласс «G», но «я» - это первый класс, поэтому он никогда не сможет найти класс «G», нет «G» верхнего «I». Поместите класс «I» между «G» и «F» :) –

+0

Пример кода неверен. 'super' имеет необходимые аргументы. – danny

+0

Внутри определения класса super() не требует аргументов. См. [Https://docs.python.org/3/library/functions.html#super](https://docs.python.org/3/library/functions.html#super) – Ben