파이썬 다중 상속(Multiple Inheritance)
파이썬에서 다중 상속은 하나의 클래스가 여러 개의 클래스로부터 상속받는 것을 말한다. 이는 하나의 클래스가 여러 부모 클래스로부터 특성과 메서드를 상속받을 수 있음을 의미한다. 예를 들어, 다음과 같은 코드에서 Bat 클래스는 Bird 클래스와 Mammal 클래스로부터 상속받고 있다.
class Bird:
def __init__(self):
self.has_wings = True
def fly(self):
print("Bird can fly")
class Mammal:
def __init__(self):
self.has_fur = True
def feed_milk(self):
print("Mammal can feed milk")
class Bat(Bird, Mammal):
def __init__(self):
Bird.__init__(self)
Mammal.__init__(self)
def hang(self):
print("Bat can hang upside down")
Bat 클래스는 Bird와 Mammal 클래스를 상속받고 각 부모 클래스의 생성자를 Bat 클래스의 생성자(__init__()) 메서드 안에서 초기화한다. 이렇게 하나의 클래스가 여러 클래스로부터 상속받을 수 있지만, 다중 상속은 사용할 때 주의가 필요하다. 클래스 간의 의존성이 복잡해질 수 있고, 이름 충돌이 발생할 수 있기 때문이다. 따라서 다중 상속을 사용할 때는 신중하게 고려해야 한다.
자식 클래스에서 부모 클래스를 상속받고 부모 클래스의 __init__() 메서드를 호출할 때 super() 메서드 또한 사용할 수 있다.
class Bird:
def __init__(self):
self.has_wings = True
def fly(self):
print("Bird can fly")
class Mammal:
def __init__(self):
self.has_fur = True
def feed_milk(self):
print("Mammal can feed milk")
class Bat(Bird, Mammal):
def __init__(self):
super().__init__() # ← 호출됨
super().__init__() # ← 호출안됨
def hang(self):
print("Bat can hang upside down")
print(Bat().has_fur)
위의 코드는 super().__init__()를 두 번 호출하고 있다. super()는 다음 부모 클래스의 메서드를 호출하는데, 여기서는 Bird 클래스와 Mammal 클래스 양쪽에서 __init__ 메서드를 호출하려고 하고 있다. 이런 경우, 파이썬의 다중 상속에서는 MRO(Method Resolution Order)에 따라서 한 번만 호출하게 된다. Bat 클래스의 MRO에 따라 super().__init__()가 먼저 나오는 클래스인 Bird 클래스의 __init__ 메서드만 호출된다.
따라서 위의 코드를 실행하면 Bat 클래스의 인스턴스에서 has_fur 속성을 찾을 수 없다는 AttributeError가 발생한다. has_fur 속성은 Mammal 클래스에 정의되어 있기 때문이다.
직접 호출
class Bat(Bird, Mammal):
def __init__(self):
Bird.__init__(self)
Mammal.__init__(self)
def hang(self):
print("Bat can hang upside down")
그래서 다중 상속의 경우, 처음 봤던 예제와 같이 명시적으로 위에 코드처럼 클래스 이름을 이용하여 __init__() 메서드를 호출하는 것이 좋을 때가 있다. 이렇게 하면 MRO에 의한 호출 순서에 영향을 받지 않고 특정 클래스의 __init__() 메서드를 직접 호출할 수 있다. 명시적인 호출을 통해 코드가 더 명확해지고 의도가 명시적으로 드러나기 때문이다.
다만, 주의할 점은 너무 복잡한 다중 상속 구조에서는 MRO에 따른 호출 순서를 이해하기 어려울 수 있으며, 이로 인해 예상치 못한 동작이 발생할 수 있다. 따라서 코드를 작성할 때는 MRO와 호출 순서를 잘 이해하고, 필요에 따라 명시적인 호출을 고려하는 것이 좋다.
super() 메서드를 사용해서도 부모 클래스의 __init__() 메서드들을 아래의 예제 코드처럼 명시적으로 호출할 수 있다.
super()를 사용한 호출
class Bat(Bird, Mammal):
def __init__(self):
super(Bird,self).__init__()
super(Mammal, self).__init__()
def hang(self):
print("Bat can hang upside down")
클래스이름.__init__()과 super(클래스이름, self).__init__() 두 방법 모두 부모 클래스의 초기화 메서드를 호출하는 방법이다. 그럼 두 방법에는 어떤 차이점이 있는지 한 번 살펴보자.
- 직접 호출 - 직접 부모 클래스의 메서드를 호출하는 경우에는 해당 클래스에 명시적으로 연결된다. 예를 들어, Bird.__init__()는 항상 Bird 클래스의 초기화 메서드를 호출한다.
- super()를 사용한 호출 - super()를 사용하는 경우에는 MRO(Method Resolution Order)에 따라 호출될 클래스가 결정된다. super(Bird, self).__init__()는 현재 클래스(Bat)의 MRO에서 Bird 다음에 오는 클래스의 초기화 메서드를 호출한다. 이렇게 함으로써 클래스의 변경에 따라 유연하게 부모 클래스를 바꿀 수 있다.
class Bird:
def __init__(self):
self.has_wings = True
def fly(self):
print("Bird can fly")
class Mammal:
def __init__(self):
self.has_fur = True
def feed_milk(self):
print("Mammal can feed milk")
class Bat(Bird, Mammal):
def __init__(self):
Bird.__init__(self)
Mammal.__init__(self)
def hang(self):
print("Bat can hang upside down")
def info(self):
print(f"Do i have wings? : {self.has_wings}\nDo i have fur? : {self.has_fur}")
bat = Bat()
bat.info()
만약 자식 클래스에서 부모 클래스의 속성에 접근하려면 위에 예제 코드처럼 self. 부모클래스속성과 같이 접근할 수 있다. 여기서 self는 자기 자신의 객체를 의미하는데 Bat 객체는 이미 Bird와 Mammal 객체를 상속받았으므로 부모 클래스의 속성에도 접근할 수 있는 것이다.
읽어주셔서 감사합니다.