Method Resolution Order (MRO)#
파이썬에서 클래스의 상속 관계에서 메서드를 찾는 순서를 정의하는 규칙으로 자식과 부모 클래스를 모두 포함하여 메서드의 실행 순서를 정한다.
이는 특히 다중 상속이 있을 때 매우 중요하다. 파이썬은 C3 선형화 알고리즘을 사용하여 이 순서를 결정한다.
MRO와 관련된 문제가 발생하면 __mro__
속성을 통해 메서드 해결 순서를 확인하고, 필요한 경우 클래스 계층 구조를 재설계하거나 명시적인 메서드 호출을 사용하여 문제를 해결할 수 있다.
동작 방식#
- 호출된 자식 클래스를 먼저 확인한다.
- 그 다음 상속된 클래스들을 나열한 순서대로 확인한다.
우선순위#
- 자식 클래스
- 부모 클래스 (먼저 상속받을수록 우선순위가 높음)
- 부모 클래스의 부모 클래스 (존재하는 경우)
- 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
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)
|
상속 순서의 중요성
1
2
3
4
5
6
7
| # 잘못된 예
class Wrong(B, A): # B가 A를 상속하는데 순서가 잘못됨
pass
# 올바른 예
class Right(A, B): # 올바른 상속 순서
pass
|
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의 메서드 호출
|
참고 및 출처#