
파이썬(Python)은 다중 상속을 허용하는 강력한 언어입니다. 하지만 여러 부모 클래스를 상속받을 때 발생하는 메서드 호출의 모호성, 즉 '다이아몬드 문제(Diamond Problem)'는 개발자들을 종종 혼란에 빠뜨립니다. 이를 해결하기 위해 파이썬은 MRO(Method Resolution Order)라는 엄격한 순서 결정 알고리즘을 사용합니다. 본 포스팅에서는 단순히 super()가 부모를 부른다는 기초 개념을 넘어, 파이썬 내부에서 C3 Linearization 알고리즘이 어떻게 경로를 계산하는지, 그리고 실무에서 발생할 수 있는 상속 충돌을 어떻게 우아하게 해결하는지 심층적으로 다룹니다.
1. MRO와 C3 Linearization: 내부 동작 원리
파이썬 2.3부터 도입된 C3 Linearization 알고리즘은 다음의 3가지 원칙을 보장합니다.
- 자식 우선(Child First): 자식 클래스는 항상 부모 클래스보다 먼저 검색됩니다.
- 단조성(Monotonicity): 부모 클래스들 간의 우선순위가 상속 구조 내에서 일관되게 유지되어야 합니다.
- 로컬 우선순위(Local Precedence): 상속 목록에 나열된 순서대로 부모 클래스를 방문합니다.
2. MRO와 super() 호출 메커니즘 비교 분석
super()는 단순히 '부모'를 찾는 것이 아니라, 현재 클래스의 MRO 리스트에서 다음 순번을 찾는 함수입니다. 이 미세한 차이가 복잡한 다중 상속 구조에서 실행 순서를 결정짓습니다.
| 비교 항목 | MRO (Method Resolution Order) | super() Call Mechanism |
|---|---|---|
| 정의 | 클래스 계층 구조의 선형화된 검색 경로 | MRO 경로상의 다음 메서드를 호출하는 동적 위임 |
| 결정 시점 | 클래스가 정의되는 시점 (정적 계산) | 런타임에 인스턴스의 MRO를 바탕으로 결정 |
| 다이아몬드 문제 | 중복 방문을 방지하는 알고리즘 제공 | 중복 호출 없이 모든 부모를 정확히 1회 방문 |
| 확인 방법 | ClassName.mro() 또는 .__mro__ |
super(CurrentClass, self).method() |
| 주요 특징 | 상속 선언 시 에러 발생 시(Inconsistent MRO) 차단 | 협력적 다중 상속(Cooperative Inheritance) 지원 |
3. 실무 최적화 및 해결을 위한 7가지 Sample Examples
실제 엔지니어링 환경에서 MRO가 어떻게 동작하는지 7가지 실전 코드를 통해 분석합니다.
Example 1: 다이아몬드 상속 구조의 실행 순서 파악
전형적인 다이아몬드 구조에서 super()가 어떻게 중복 호출을 피하는지 보여줍니다.
class A:
def __init__(self):
print("A entry")
print("A exit")
class B(A):
def __init__(self):
print("B entry")
super().__init__()
print("B exit")
class C(A):
def __init__(self):
print("C entry")
super().__init__()
print("C exit")
class D(B, C):
def __init__(self):
print("D entry")
super().__init__()
print("D exit")
# 실행 결과: D -> B -> C -> A 순으로 진입
d = D()
print(D.mro())
Example 2: 믹스인(Mixin) 클래스와 MRO의 결합
로그 기록이나 권한 체크 등 공통 기능을 믹스인으로 주입할 때의 상속 순서 제어 방법입니다.
class LoggingMixin:
def log(self, message):
print(f"[LOG]: {message}")
class BaseWorker:
def work(self):
print("Working...")
class SmartWorker(LoggingMixin, BaseWorker):
def work(self):
self.log("Starting work")
super().work()
worker = SmartWorker()
worker.work() # LoggingMixin이 BaseWorker보다 앞에 위치함
Example 3: Inconsistent MRO 에러 해결 방법
상속 순서가 꼬여 파이썬이 클래스 생성을 거부할 때 이를 교정하는 방법입니다.
class X: pass
class Y(X): pass
# class Z(X, Y): pass # TypeError 발생: MRO 순서 모순
# 올바른 해결: 구체적인 자식(Y)을 추상적인 부모(X)보다 앞에 둡니다.
class Z(Y, X): pass
print(Z.mro())
Example 4: super()에 인자를 전달하는 고급 기법
다중 상속 구조에서 각 부모 클래스가 요구하는 인자가 다를 때 **kwargs를 활용하는 패턴입니다.
class Base:
def __init__(self, **kwargs):
print("Base init")
class Left(Base):
def __init__(self, left_val, **kwargs):
print(f"Left init: {left_val}")
super().__init__(**kwargs)
class Right(Base):
def __init__(self, right_val, **kwargs):
print(f"Right init: {right_val}")
super().__init__(**kwargs)
class Combined(Left, Right):
def __init__(self):
super().__init__(left_val="L", right_val="R")
# 모든 인자가 MRO를 타고 정확히 전달됨
c = Combined()
Example 5: 특정 부모를 건너뛰는 super() 바인딩
특정 계층을 건너뛰고 상위 부모를 명시적으로 지칭하고 싶을 때(주의해서 사용)의 문법입니다.
class Parent:
def greet(self):
print("Hello from Parent")
class Child(Parent):
def greet(self):
print("Hello from Child")
# Parent의 greet를 직접 호출하지 않고 MRO를 우회
super(Child, self).greet()
Example 6: 동적 클래스 생성 시 MRO 확인
type() 함수를 사용해 동적으로 클래스를 만들 때 MRO가 어떻게 구성되는지 확인합니다.
DynamicClass = type('DynamicClass', (B, C), {})
print(f"Dynamic MRO: {DynamicClass.mro()}")
# [DynamicClass, B, C, A, object]
Example 7: 추상 클래스(ABC)와 super()의 조합
인터페이스 정의와 기본 구현이 섞인 환경에서 super()를 통해 기본 로직을 호출하는 방식입니다.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def draw(self):
print("Drawing Shape Base")
class Circle(Shape):
def draw(self):
super().draw() # 추상 메서드의 기본 구현 호출 가능
print("Drawing Circle")
Circle().draw()
4. 결론: 왜 MRO를 이해해야 하는가?
파이썬의 super()는 단순히 부모 클래스로의 상향 링크가 아니라, 전체 상속 그래프를 일렬로 세운 뒤 '다음 사람'을 찾는 지침서입니다. 이를 이해하지 못하면 다중 상속 시 특정 부모의 메서드가 누락되거나, 의도치 않은 순서로 실행되어 디버깅이 불가능한 '좀비 코드'가 양산될 수 있습니다. 선형화된 순서를 이해하는 것은 대규모 프레임워크(Django, FastAPI 등)의 내부 구조를 이해하는 필수 역량입니다.
5. 참고 문헌 및 자료 출처
- Python Software Foundation. "The Python 2.3 Method Resolution Order".
- Raymond Hettinger. "Python’s super() considered super!".
- Guido van Rossum. "Method Resolution Order in Python 3".
- C3 Linearization Algorithm (Wikipedia).