[python] 새 스타일 클래스의 MRO (Method Resolution Order)?

Python in a Nutshell (2nd Edition) 책 에는
기존 스타일 클래스를 사용 하여 메서드가 고전적인 해결 순서로 해결되는
방법과 새로운 순서 와 어떻게 다른지 보여주는 예제 가 있습니다.

새 스타일로 예제를 다시 작성하여 동일한 예제를 시도했지만 결과는 이전 스타일 클래스에서 얻은 것과 다르지 않습니다. 예제를 실행하는 데 사용하는 파이썬 버전은 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입니다. 따라서 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, 한 번만와 그 서브 클래스의 모든 후 확인 순서에 와서 강제로 너무 재 지정 (회원의 C의 재정, 즉 것을 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

ABDCEFG를 받았습니까?

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


답변

당신이 얻는 결과는 정확합니다. Base3to의 기본 클래스를 변경 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

자세한 정보는 이 설명 을 읽으십시오 .


답변

메서드 확인은 너비 우선이 아니라 깊이 우선이기 때문에 이러한 동작을 볼 수 있습니다. Dervied의 상속은 다음과 같습니다.

         Base2 -> Base1
        /
Derived - Base3

그래서 instance.amethod()

  1. Base2를 확인하고 amethod를 찾지 못했습니다.
  2. Base2가 Base1에서 상속되었는지 확인하고 Base1을 확인합니다. Base1에는이 amethod있으므로 호출됩니다.

이것은에 반영됩니다 Derived.__mro__. 단순히 반복하고 Derived.__mro__찾는 방법을 찾으면 중지하십시오.


답변