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


94

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를 더 잘 이해하도록 도와주세요.

답변:


184

레거시 클래스와 새로운 스타일 클래스의 해결 순서 간의 결정적인 차이는 동일한 조상 클래스가 "순진한"깊이 우선 접근 방식에서 두 번 이상 발생할 때 발생합니다. 예를 들어 "다이아몬드 상속"사례를 고려하십시오.

>>> 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) 실제로 현명하게 작동합니다.

이것이 구식 클래스를 피해야하는 이유 중 하나입니다. "다이아몬드와 같은"패턴을 사용한 다중 상속은 새 스타일에서는 제대로 작동하지 않습니다.


2
"[조상 클래스] A [은] 모든 하위 클래스 이후에 한 번만 해결 순서로 오도록 강요하므로 오버라이드 (즉, 멤버 x에 대한 C의 오버라이드)가 실제로 현명하게 작동합니다." - 주현절! 이 문장 덕분에 머릿속에서 MRO를 다시 할 수 있습니다. \ o / 정말 감사합니다.
Esteis 2015

23

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

두 번째 코드를 수정해야합니다. 클래스 "I"를 첫 번째 줄로 입력하고 super를 사용하여 수퍼 클래스 "G"를 찾았지만 "I"는 첫 번째 클래스이므로 "G"클래스를 찾을 수 없습니다. "G"상위 "I"가 아닙니다. "I" "G"와 "F": 사이에 넣고 클래스
Aaditya 우라

예제 코드가 올바르지 않습니다. super필수 인수가 있습니다.
danny

2
클래스 정의 내부에서 super ()에는 인수가 필요하지 않습니다. 참조 https://docs.python.org/3/library/functions.html#super

그래프 이론은 불필요하게 복잡합니다. 1 단계 후 왼쪽에있는 클래스에서 오른쪽에있는 클래스 (상속 목록에 있음)로 가장자리를 삽입 한 다음 토폴로지 정렬 을 수행하면 완료됩니다.
Kevin

@Kevin 나는 그것이 옳다고 생각하지 않는다. 내 예를 따르면 ACDBEFGH가 유효한 토폴로지 정렬이 아닐까요? 그러나 그것은 해결 순서가 아닙니다.
Ben

5

당신이 얻는 결과는 정확합니다. 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

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


1

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

         Base2 -> Base1
        /
Derived - Base3

그래서 instance.amethod()

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

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


내가 대답으로 "Base1"을 얻는 이유는 메소드 해상도가 깊이 우선이기 때문인지 의심 스럽습니다. 깊이 우선 접근법보다 더 많은 것이 있다고 생각합니다. Denis의 예를 참조하십시오. 깊이가 처음 인 경우 o / p는 "Base1"이어야합니다. 또한 제공 한 링크의 첫 번째 예를 참조하십시오. 표시된 MRO는 메서드 해상도가 깊이 우선 순서로만 순회하여 결정되지 않음을 나타냅니다.
sateesh 2009

Denis가 MRO 문서에 대한 링크를 제공했습니다. python.org에 대한 링크를 제공했다고 착각했습니다.
sateesh 2009

4
일반적으로 깊이 우선이지만 Alex가 설명했듯이 다이아몬드와 같은 상속을 처리하는 현명한 방법이 있습니다.
jamessan 2009
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.