ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Python - MRO(Method Resolution Order)
    Python 2019. 9. 7. 17:48

    Django REST Framework 문서에서 mixin 부분을 읽다가 예전에 공부 했었던 MRO(Method Resolution Order)에 대해 정리해 두는것이 좋을 것 같아 오랜만에 글을 쓰게 되었습니다.

     

    Method Resolrution Order

    파이썬은 다중 상속이 가능합니다. C라는 클래스가 A, B라는 클래스를 상속받고, A와 B 클래스에 met이라는 메서드를 가지고 있을 경우, C.met()은 어떤 클래스의 메소드를 실행 시킬까요? 파이썬에서는 이러한 메서드의 탐색 순서를 결정할 수 있도록 다중 상속 시, __mro__ 라는 속성에 메서드의 호출 순서가 정렬됩니다. 

     

    예를 들어 class C(A, B)로 정의될 경우 실행 순서(MRO)는 왼쪽에서 오른쪽으로 C, A, B가 됩니다. 만일 파이썬이 MRO를 지원하지 않거나 메서드의 탐색 순서를 결정할 수 없을 경우, 다이아몬드 상속 문제 또는 죽음의 다이아몬드가 발생하게 됩니다. 최상위 부모(object)로부터 상속받은 다수의 클래스(A, B)를 상속 받는 자식 클래스(C)가 동일한 속성 또는 메서드(A.met()? B.met()??)를 가지고 있는 부모(A or B)를 어떻게 선택할 것인가?? 파이썬에서는 이러한 문제를 MRO를 이용해 해결할 수 있습니다.

     

    MRO는 다음 조건을 통해 MRO 리스트를 정렬합니다.

    조건1. 자식 클래스를 부모보다 먼저 확인

    조건2. 부모 클래스가 둘 이상이면 리스팅한 순서대로 확인

    조건3. 유효한 후보가 두 가지 있으면, 첫 번째 부모 클래스부터 선택 

    +조건. 상속을 받을 때, 클래스의 부모가 서로 교차될 경우 'TypeError: Cannot create a consistent # method resolution order(MRO)'를 일으킨다(단, 중복은 허용된다).

    class A:
        pass
    
    class B(A):
        pass
    
    class C(A):
        pass
    
    class D(B, C):
        pass
    
    class D1(C, B):
        pass
    
    class E(C, B):
        pass
    #
    # class F(D, E):
    #     pass  # 정의 불가!! - 상속 시 부모의 교차
    
    class F1(D1, E):
        pass  # None Error - 상속 시 부모 중복
    
    
    # print(F.mro())
    print(F1.mro())

     

     

    간단한 예제를 통해 좀 더 알아보겠습니다.

     

    class A:
        def met(self):
            print('A-class')
    
    
    print(A.__mro__)
    
    # Output :
    # (<class '__main__.A'>, <class 'object'>)

    모든 클래스의 최상위 클래스는 object이므로 가장 오른쪽에 위치하는 클래스는 'object'가 됩니다.

     

    아래는 다중 상속 시 MRO에 의해 어떻게 처리되는지를 나타내는 간단한 코드 입니다.

    class A:
        def met(self):
            print('A-class')
            super().met()
    
    class B:
        def met(self):
            print('B-class')
    
    class C(B, A):  
        pass
    
    
    c = C()
    c.met()
    print(C.__mro__)
    
    # Output:
    # B-class
    # (<class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>)

    C 클래스는 B, A 순서로 클래스를 상속 받습니다. A 클래스는 super()를 이용해 부모의 met()을 호출하고, B 클래스는 내부에 정의된 met() 메서드를 갖는 간단한 구조의 클래스입니다. 위 예제를 실행할 경우, B는 실행되지만 A는 실행되지 않습니다.

     

    이와 달리 C 클래스가 A, B 순서로 클래스를 상속 받을 경우 어떻게 되는지 아래의 예제로 확인해보겠습니다.

    class A:  
        def met(self):
            print('A-class')
            super().met()
    
    
    class B:  
        def met(self):
            print('B-class')
    
    
    class C(A, B):  
        pass
        
    c = C()
    c.met()
    print(C.__mro__)
    
    # Output:
    # A-class
    # B-class
    # (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)

    MRO는 순서대로 C, A, B, object가 됩니다. 클래스 A의 메소드는 자신의 부모(super)의 메서드(met())를 호출하도록 되어있습니다. A클래스의 실제 부모 객체는 object지만 MRO에 의해 A의 super()는 B 클래스를 가리키게 됩니다. 따라서 C, A(super->B), B의 순서대로 실행되고 위와 같이 출력됩니다. 

     

    아래와 같이 A 클래스에 super().met(), B 클래스에 super().met()을 정의할 경우 어떻게 실행될까요?

    class A:  
        def met(self):  # 4
            print('A-class')  
            super().met()  # 5
    
    class B:  
        def met(self):  # 2
            print('B-class')  
            super().met()  # 3
    
    class C(B, A): 
        pass
    
    c = C()
    c.met()  # 1
    print(C.__mro__)

    MRO는 C, B, A, object가 됩니다. 실행 순서는 #1 부터 #5가 되지만, 문제는 #5가 실행 될 때 발생합니다. #5에서 실행될 super().met()은 A의 부모(object)의 met() 메서드를 가리키게 됩니다. 하지만 object에는 met() 메서드는 정의되어있지 않기 때문에 아래와 같은 에러 메세지가 출력됩니다.

    B-class
    A-class
    Traceback (most recent call last):
      ...
    AttributeError: 'super' object has no attribute 'met'

     

     

    제가 이해할 수 있는 선에서 간단하게 MRO에 대해 정리하였습니다. 복잡한 상속관계를 갖고있는 클래스나 구조에 대해 설명하기엔 아직 이해가 되지 않고 설명하기 어려운 부분이 많지만, 공부를 계속하면서 추가 내용을 업로드 할 예정입니다.

     

     

    mro는 예전에 공부해두었지만 개발하면서 이 개념을 토대로 코드에 적용해 본 적은 없었습니다(정확히는 어려워서 사용하지 못했습니다...). DRF 문서에 generic view의 Mixin을 읽던 중 "특정 클래스를 view에 상속시킴으로써 기능을 추가할 수 있다"는 내용을 보면서 과거에 정리해두었던 mro를 다시한번 보게 되었고, 정리하고 이해해두면 나중에 겪게될 문제 해결에 도움이 될 것 같아 이렇게 글로 작성하게 되었습니다. 먼 미래에(?) 복잡한 상속 관계가 적용된 클래스를 작성하거나 framework에서 customized mixin을 구성하게 될 때 이 글을 다시 한번 읽어보지 않을까 생각합니다.

     

    참고
    전문가를 위한 파이썬 - Chapter 12.2 다중 상속과 메서드 결정 순서
    Python Cookbook - Chapter 8.7. Calling a Method on a Parent Class
    C++ 상속, 다이아몬드 문제란? 그리고 피하는 방법 - 코딩 기록

     

     

     

     

    'Python' 카테고리의 다른 글

    객체로서의 함수  (0) 2019.08.18

    댓글

Designed by Tistory.