본문 바로가기

다중 상속

1. 다중 상속

다중 상속은 여러 부모 클래스로부터 상속받는 것입니다. 메서드 호출 순서는 MRO(Method Resolution Order)로 결정되며, '클래스.mro()'로 확인할 수 있습니다.

다중 상속은 한 클래스가 여러 부모 클래스로부터 상속을 받는 것을 의미합니다. 이를 통해 여러 부모 클래스의 특성과 기능을 하나의 자식 클래스에 결합할 수 있습니다. 기본 골격은 아래와 같습니다.

class Parent1:
    pass

class Parent2:
    pass

class Child(Parent1, Parent2):
    pass

Child 클래스는 Parent1 및 Parent2 두 부모 클래스로부터 상속을 받습니다.

2. 다중 상속 예제

다중 상속을 사용할 때 가장 중요한 문제 중 하나는 메서드의 호출 순서입니다. 만약 두 부모 클래스가 동일한 메서드 이름을 가지고 있다면, 자식 클래스 객체에서 해당 메서드를 호출할 때 어느 부모의 메서드를 호출해야 하는지 결정해야 합니다.

파이썬은 이 문제를 해결하기 위해 Method Resolution Order(MRO)를 도입하였고, mro()에 따라 호출 순서를 결정합니다.

class A:
    def method(self):
        print("A method")

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

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

class D(B, C):
    pass

obj = D()
print(D.mro()) # 출력: [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
obj.method()  # 출력: B method

여기서 D(B, C)를 D(C, B)로 바꾸면 호출되는 값이 달라집니다. 따라서 obj.method()를 출력하면 mro 순서에서 자신에게 가장 가까운 곳에 정의되어 있는 메서드를 가지고 오게 됩니다.

class A:
    def method(self):
        print("A method")

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

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

class D(C, B):
    pass

obj = D()
print(D.mro()) # 출력: [<class '__main__.D'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.A'>, <class 'object'>]
obj.method()  # 출력: C method

위 상속 관계는 아래처럼 그림으로 표현할 수 있습니다.

위 그림에서 D가 hello를 찾는 길은 두 갈래입니다. D에 없으니 B로 가고, B에도 없으니 여기서 갈림길이 생깁니다. 바로 위로 올라가 A에서 찾을 것인가, 아니면 옆으로 건너가 C에서 먼저 찾을 것인가입니다.

파이썬 3의 MRO는 공통 조상인 A를 마지막으로 미룹니다. 그래서 D → B → C → A 순서가 되고, C에 있는 hello를 먼저 만나 'hello C'가 출력됩니다.

이러한 형태는 마치 다이아몬드처럼 생겼다 하여 다이아몬드 상속이라 불립니다. 이 다이아몬드 상속에서 주의해야 할 점은 python 2.x와 3.x가 순서가 다르다는 것입니다. 3.x에서는 hello C가, 2.x에서는 hello A가 출력됩니다. 다만 2.x에서 class A:를 class A(object):로 바꾸면 실행 순서는 같아집니다. 상속 파트에서 언급한 것처럼 python 2.x에서는 object를 명시적으로 써주어야 새로운 스타일의 object를 상속하기 때문입니다.

class A:
    def hello(self):
        print('hello A')

class B(A):
    pass

class C(A):
    def hello(self):
        print('hello C')

class D(B, C):
    pass

d = D()
d.hello()

조금 어려운 문제를 살펴보도록 하겠습니다. 앞서 다뤘던 Car class를 이용해 아래 그림과 같은 다중 상속을 구현해보도록 하겠습니다.

이 순서를 잘 보시면 HybridCar는 상속을 받을 이유가 없습니다. ElectricCar에서 이미 HybridCar를 상속받고 있기 때문입니다. 그럼에도 불구하고 상속을 받아보도록 하겠습니다.

class Car(object):
    pass

class HybridCar(Car):
    pass

class ElectricCar(HybridCar):
    pass

class HybridElectricCar(ElectricCar, HybridCar):
    pass

modelz = HybridElectricCar()

위처럼 작성 되었을 때에는 문제가 생기지 않습니다. 이는 MRO에 문제가 생기지 않기 때문입니다. 다만 아래와 같이 순서를 바꾸면 에러가 발생됩니다. ElectricCar의 부모인 HybridCar를 먼저 참조했기 때문입니다. 부모와 조부모(부모의 부모)가 함께 상속되었는데 조부모(부모의 부모)를 먼저 참조하는 것은 MRO 순서에 어긋나게 됩니다.

class Car(object):
    pass

class HybridCar(Car):
    pass

class ElectricCar(HybridCar):
    pass

class HybridElectricCar(HybridCar, ElectricCar):
    pass

modelz = HybridElectricCar()
# 출력
TypeError: Cannot create a consistent method resolution
order (MRO) for bases HybridCar, ElectricCar

파이썬은 상속 목록에 적힌 순서를 지키면서도 '자식이 부모보다 항상 앞에 온다'는 규칙을 만족하는 순서를 만들어야 합니다. 그런데 HybridElectricCar(HybridCar, ElectricCar)는 목록상 HybridCar가 먼저인데, 상속 관계상 ElectricCar가 HybridCar의 자식이므로 앞에 와야 합니다. 두 요구가 충돌해 순서를 만들 수 없으니 클래스 정의 자체가 실패합니다.

정리하면, 자식 클래스를 먼저 적고 부모 클래스를 나중에 적어야 합니다.