Skip to main content

상속과 다형성

학습 목표

  • 상속(Inheritance)으로 코드를 재사용할 수 있다
  • super()로 부모 클래스의 메서드를 호출할 수 있다
  • MRO(Method Resolution Order)를 이해한다
  • 다형성(Polymorphism)을 활용하여 유연한 코드를 작성할 수 있다

왜 중요한가

ML 프레임워크는 상속 기반으로 설계되어 있습니다. PyTorch에서 nn.Module을 상속하여 모델을 만들고, Dataset을 상속하여 데이터셋을 정의합니다. 상속과 다형성을 이해해야 프레임워크를 효과적으로 활용할 수 있습니다.

기본 상속

# 부모 클래스 (Base/Super class)
class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        raise NotImplementedError("하위 클래스에서 구현하세요")

    def info(self):
        return f"{self.name} ({type(self).__name__})"

# 자식 클래스 (Derived/Sub class)
class Dog(Animal):
    def speak(self):
        return f"{self.name}: 멍멍!"

class Cat(Animal):
    def speak(self):
        return f"{self.name}: 야옹!"

dog = Dog("바둑이")
cat = Cat("나비")
print(dog.speak())   # 바둑이: 멍멍!
print(cat.info())    # 나비 (Cat) — 부모의 info() 상속

# isinstance로 상속 관계 확인
print(isinstance(dog, Dog))     # True
print(isinstance(dog, Animal))  # True
print(isinstance(dog, Cat))     # False

super() 함수

super()는 부모 클래스의 메서드를 호출합니다.
class Shape:
    def __init__(self, color="black"):
        self.color = color

    def describe(self):
        return f"색상: {self.color}"

class Rectangle(Shape):
    def __init__(self, width, height, color="black"):
        super().__init__(color)    # 부모의 __init__ 호출
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def describe(self):
        base = super().describe()  # 부모의 describe 호출
        return f"{base}, 넓이: {self.area()}"

rect = Rectangle(10, 5, "blue")
print(rect.describe())  # 색상: blue, 넓이: 50

메서드 오버라이딩

class Logger:
    def log(self, message):
        print(f"[LOG] {message}")

class TimestampLogger(Logger):
    def log(self, message):
        """부모 메서드를 오버라이딩하여 타임스탬프 추가"""
        import datetime
        now = datetime.datetime.now().strftime("%H:%M:%S")
        super().log(f"[{now}] {message}")

logger = TimestampLogger()
logger.log("서버 시작")  # [LOG] [14:30:25] 서버 시작

다중 상속과 MRO

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

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

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

class D(B, C):
    pass

d = D()
print(d.method())  # "B"

# MRO (Method Resolution Order) 확인
print(D.__mro__)
# (<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)

# C3 선형화 알고리즘에 따라 D -> B -> C -> A -> object 순서

다형성 (Polymorphism)

다형성은 같은 인터페이스로 다른 동작을 수행하는 것입니다.
class Shape:
    def area(self):
        raise NotImplementedError

class Circle(Shape):
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14159 * self.radius ** 2

class Rectangle(Shape):
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

# 다형성: 같은 메서드 이름, 다른 동작
shapes = [Circle(5), Rectangle(4, 6), Circle(3)]

for shape in shapes:
    print(f"{type(shape).__name__}: 넓이 = {shape.area():.2f}")
# Circle: 넓이 = 78.54
# Rectangle: 넓이 = 24.00
# Circle: 넓이 = 28.27

# 함수에서 활용 - 타입에 관계없이 동작
def total_area(shapes):
    return sum(s.area() for s in shapes)

print(f"전체 넓이: {total_area(shapes):.2f}")

AI/ML에서의 활용

# PyTorch nn.Module 상속 패턴 (의사 코드)
class BaseModel:
    def __init__(self):
        self.layers = []

    def forward(self, x):
        raise NotImplementedError

    def predict(self, x):
        return self.forward(x)

class LinearModel(BaseModel):
    def __init__(self, input_dim, output_dim):
        super().__init__()
        self.weight = [[0.0] * input_dim for _ in range(output_dim)]

    def forward(self, x):
        # 선형 변환
        return x  # 단순화

class MLPModel(BaseModel):
    def __init__(self, input_dim, hidden_dim, output_dim):
        super().__init__()
        self.linear1 = LinearModel(input_dim, hidden_dim)
        self.linear2 = LinearModel(hidden_dim, output_dim)

    def forward(self, x):
        x = self.linear1.forward(x)
        x = self.linear2.forward(x)
        return x

# 다형성: 어떤 모델이든 같은 인터페이스
def evaluate(model, test_data):
    """모델 타입에 관계없이 동일한 평가 코드"""
    predictions = model.predict(test_data)
    return predictions

linear = LinearModel(784, 10)
mlp = MLPModel(784, 128, 10)

# 같은 함수로 다른 모델 평가
evaluate(linear, test_data)
evaluate(mlp, test_data)
Python은 다중 상속을 지원하지만, 복잡성이 크게 증가합니다. Mixin 패턴(작은 기능 단위의 클래스를 조합)으로 제한적으로 사용하는 것이 좋습니다. 대부분의 경우 단일 상속 + 조합(Composition)이 더 나은 설계입니다.
“is-a” 관계이면 상속, “has-a” 관계이면 조합을 사용합니다. Dog is an Animal -> 상속, Car has an Engine -> 조합. 일반적으로 조합을 먼저 고려하세요.

체크리스트

  • 상속으로 부모 클래스의 기능을 재사용할 수 있다
  • super()로 부모 클래스의 메서드를 호출할 수 있다
  • MRO의 개념과 검색 순서를 이해한다
  • 다형성을 활용하여 유연한 코드를 작성할 수 있다

다음 문서