Method Resolution Order (MRO)

파이썬에서 클래스의 상속 관계에서 메서드를 찾는 순서를 정의하는 규칙으로 자식과 부모 클래스를 모두 포함하여 메서드의 실행 순서를 정한다.
이는 특히 다중 상속이 있을 때 매우 중요하다. 파이썬은 C3 선형화 알고리즘을 사용하여 이 순서를 결정한다.

MRO와 관련된 문제가 발생하면 __mro__ 속성을 통해 메서드 해결 순서를 확인하고, 필요한 경우 클래스 계층 구조를 재설계하거나 명시적인 메서드 호출을 사용하여 문제를 해결할 수 있다.

동작 방식

  • 호출된 자식 클래스를 먼저 확인한다.
  • 그 다음 상속된 클래스들을 나열한 순서대로 확인한다.

우선순위

  1. 자식 클래스
  2. 부모 클래스 (먼저 상속받을수록 우선순위가 높음)
  3. 부모 클래스의 부모 클래스 (존재하는 경우)
  4. object 클래스 (최상위)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class A:
    def method(self):
        print("A's method")

class B(A):
    def method(self):
        print("B's method")

class C(A):
    def method(self):
        print("C's method")

class D(B, C):
    pass

# MRO 확인
print(D.__mro__)
# 출력: (<class '__main__.D'>, <class '__main__.B'>, 
#        <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

d = D()
d.method()  # B's method가 출력됨

다이아몬드 문제와 MRO

다이아몬드 문제는 다중 상속에서 발생할 수 있는 고전적인 문제. 파이썬의 MRO는 이 문제를 해결하는 방법을 제공한다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Base:
    def method(self):
        print("Base method")

class Left(Base):
    def method(self):
        print("Left method")

class Right(Base):
    def method(self):
        print("Right method")

class Child(Left, Right):
    pass

# MRO 확인
print(Child.__mro__)
# 출력: (<class '__main__.Child'>, <class '__main__.Left'>, 
#        <class '__main__.Right'>, <class '__main__.Base'>, <class 'object'>)

child = Child()
child.method()  # Left method가 출력됨

C3 선형화 알고리즘 (C3 Linearization Algorithm)

super() 함수와 MRO

super()는 MRO를 활용하여 상위 클래스의 메서드를 호출한다.
이를 통해 메서드 체인을 구현할 수 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class A:
    def method(self):
        print("A's method")
        
class B(A):
    def method(self):
        print("B's method")
        super().method()
        
class C(A):
    def method(self):
        print("C's method")
        super().method()
        
class D(B, C):
    def method(self):
        print("D's method")
        super().method()

d = D()
d.method()
# 출력:
# D's method
# B's method
# C's method
# A's method

MRO의 실제 활용 사례

믹스인 패턴 구현

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class LoggerMixin:
    def log(self, message):
        print(f"[LOG] {message}")
        
class APIAccessMixin:
    def api_call(self, endpoint):
        self.log(f"Calling API: {endpoint}")
        # API 호출 로직
        
class UserService(APIAccessMixin, LoggerMixin):
    def get_user(self, user_id):
        self.api_call(f"/users/{user_id}")

service = UserService()
service.get_user(123)
# 출력: [LOG] Calling API: /users/123

다중 상속에서의 초기화

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Database:
    def __init__(self, db_url):
        self.db_url = db_url
        print(f"Database initialized with {db_url}")

class Cache:
    def __init__(self, cache_url):
        self.cache_url = cache_url
        print(f"Cache initialized with {cache_url}")

class Service(Database, Cache):
    def __init__(self, db_url, cache_url):
        Database.__init__(self, db_url)
        Cache.__init__(self, cache_url)

# 더 나은 방법: super() 사용
class BetterService(Database, Cache):
    def __init__(self, db_url, cache_url):
        super().__init__(db_url)
        Cache.__init__(self, cache_url)

MRO 관련 주의사항

  1. 일관성 있는 메서드 시그니처

    1
    2
    3
    4
    5
    6
    7
    
    class Parent:
        def method(self, x, y):
            pass
    
    class Child(Parent):
        def method(self, x, y):  # 동일한 파라미터 유지
            super().method(x, y)
    
  2. 상속 순서의 중요성

    1
    2
    3
    4
    5
    6
    7
    
    # 잘못된 예
    class Wrong(B, A):  # B가 A를 상속하는데 순서가 잘못됨
        pass
    
    # 올바른 예
    class Right(A, B):  # 올바른 상속 순서
        pass
    
  3. MRO 충돌 해결

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    class A:
        def method(self): print("A")
    
    class B(A):
        def method(self): print("B")
    
    class C(A):
        def method(self): print("C")
    
    # 이렇게 하면 MRO 충돌 발생
    # class Conflict(B, C): pass
    
    # 해결 방법: 명시적 메서드 구현
    class Resolved(B, C):
        def method(self):
            B.method(self)  # 명시적으로 B의 메서드 호출
    

참고 및 출처