질문에 대한 답변.
파이썬 2.3 이전에는 메서드 해결이 깊이 우선, 왼쪽부터 오른쪽으로 검색하는 방식에 의존하여 다이아몬드 상속 패턴에서 일관성 없는 결과를 낳았습니다. 이를 대체하기 위해 다이셀 프로그래밍 언어를 위해 원래 개발된 C3 선형화 알고리즘이 채택되었습니다. 이 알고리즘은 상속 그래프와 기본 클래스의 선언 순서를 모두 존중하는 수학적으로 엄격한 순서를 제공합니다.
다중 상속 시나리오에서는 부모가 항상 자식보다 먼저 나오고 모든 수준에서 왼쪽에서 오른쪽으로의 선언 순서가 유지되는 결정론적 선형화를 요구합니다. 알고리즘은 또한 단조성을 유지해야 하며, 클래스 A가 부모의 MRO에서 클래스 B보다 먼저 오는 경우, 이 순서는 어떤 서브클래스에서도 뒤집힐 수 없습니다. 특정 상속 선언은 이러한 제약 조건이 충돌하는 논리적 모순을 생성하여 유효한 선형화를 불가능하게 만듭니다.
C3는 모든 부모 클래스의 선형화와 부모 목록을 병합하여 MRO를 계산합니다. 알고리즘은 이러한 목록에서 다른 목록의 꼬리에 나타나지 않는 첫 번째 헤드를 재귀적으로 선택하여 어떤 클래스도 자신의 전제 조건보다 먼저 배치되지 않도록 보장합니다. 어떤 단계에서도 유효한 헤드가 존재하지 않으면, 파이썬은 일관성 없는 메서드 해결 순서를 나타내는 TypeError를 발생시킵니다.
class A: pass class B(A): pass class C(A): pass class D(B, C): pass # D.__mro__는 다음과 같이 계산됩니다: merge(L(B), L(C), [B, C]) # 결과: (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>) print(D.__mro__)
실생활의 상황
로그 기록 및 검증과 같은 교차 절단 문제를 추가하기 위해 믹스인 클래스를 사용하여 데이터 처리 프레임워크를 설계하고 있었습니다. 우리의 기본 클래스 DataProcessor는 핵심 기능을 제공했으며, LoggingMixin과 CacheMixin은 모두 공유 유틸리티를 위해 BaseComponent로부터 상속받았습니다. 구체적인 클래스가 이러한 믹스인을 결합할 때, 로깅 전에 캐싱이 발생하는 초기화 순서 버그에 직면했으며, BaseComponent 메서드가 다양한 구체적 구현에서 일관성 없게 해결되었습니다.
첫 번째로 고려한 솔루션은 각 구체적 클래스에서 수동 메서드 체이닝으로, LoggingMixin.process()를 하드코딩된 순서로 호출하고 CacheMixin.process()를 호출하는 것이었습니다. 이 접근 방식은 실행 순서에 대한 명시적인 제어를 제공하고 MRO의 불확실성을 제거했습니다. 그러나 이로 인해 DRY 원칙이 위배되었으며, 코드베이스 전반에 걸쳐 의존성 지식이 분산되어 유지 관리 문제를 일으켰고, 동적 디스패치 시스템을 우회함으로써 다형성이 깨졌습니다.
두 번째 접근 방식은 zero-argument super() 대신 명명된 클래스를 갖는 explicit super(LoggingMixin, self) 호출을 사용하는 것이었습니다. 이를 통해 MRO와 관계없이 어떤 부모 클래스가 다음에 오는지 정확하게 제어할 수 있었습니다. 이 방법은 동작했지만, 클래스 이름을 바꾸면 모든 super() 호출을 업데이트해야 하므로 매우 취약하였고, 파이썬의 자동 선형화를 완전히 무효화하여 광범위한 리팩토링 없이는 미래의 믹스인 추가와 호환되지 않게 만들었습니다.
세 번째 접근 방식은 상속을 class Pipeline(LoggingMixin, CacheMixin, DataProcessor)로 선언하고 각 믹스인의 __init__이 super().init()을 호출하도록 구현하여 C3 선형화를 수용하는 것이었습니다. 이를 통해 MRO가 자연스럽게 LoggingMixin이 CacheMixin보다 먼저 오도록 결정했으며, DataProcessor는 마지막에 유지되었습니다. 이 솔루션은 파이썬의 상속 의미를 존중하며, 하드코딩된 클래스 참조가 필요 없고, 클래스 헤더를 업데이트하기만 하면 새로운 믹스인을 자동으로 수용할 수 있게 해주었습니다.
우리는 세 번째 솔루션을 선택했습니다. 이는 파이썬의 설계 철학과 일치하며, 그에 맞서 싸우지 않았기 때문입니다. zero-argument super()를 활용하여 각 믹스인이 MRO에서 어떤 클래스인지 모른 채로 다음 클래스에 초기화 제어를 전달할 수 있게 하여 진정한 조합 가능성을 제공했습니다. 클래스 선언의 명시적인 순서는 우선 관계를 가시적이고 유지 관리 가능하게 만들었습니다.
결과적으로 30개 이상의 프로세서 변형과 다양한 믹스인 조합을 지원하는 강력한 프레임워크가 탄생했습니다. 개발자는 초기화 순서 버그에 대해 걱정 없이 선언적으로 새로운 파이프라인 유형을 생성할 수 있었습니다. C3는 개발자가 일관성 없는 상속 패턴을 생성하려 할 때 클래스 정의 시 TypeError를 발생시켜 아키텍처 오류를 방지하고, 개발 중에 논리적 모순을 잡을 수 있게 해주었습니다.
후보자들이 자주 놓치는 점
파이썬의 C3 선형화 알고리즘이 "일관성 있는 메서드 해결 순서를 만들 수 없습니다" 오류로 특정 다중 상속 계층을 거부하는 이유는 무엇이며, 기본 상속 요구 사항을 수정하지 않고 이를 해결할 수 있는 방법은 무엇입니까?