Launguage

Python 상속과 다형성 완벽 가이드

Somaz 2025. 12. 5. 00:00
728x90
반응형

Overview

상속(Inheritance)과 다형성(Polymorphism)은 객체지향 프로그래밍의 핵심 개념이다. 코드 재사용성을 높이고, 유지보수를 쉽게 만들며, 확장 가능한 구조를 설계할 수 있게 해준다.

 

 

 

 

 

 


 

 

 

 

1. 상속 기본 개념

상속은 기존 클래스의 속성과 메서드를 새로운 클래스가 물려받는 것을 말한다.

 

 

 

기본 상속 구조

class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        return "동물이 소리를 냅니다"
    
    def move(self):
        return f"{self.name}이(가) 움직입니다"

class Dog(Animal):  # Animal을 상속
    def speak(self):  # 메서드 오버라이딩
        return "멍멍!"

class Cat(Animal):
    def speak(self):
        return "야옹!"

# 사용
dog = Dog("바둑이")
cat = Cat("나비")

print(dog.name)      # 바둑이 (상속받은 속성)
print(dog.speak())   # 멍멍! (오버라이딩된 메서드)
print(dog.move())    # 바둑이이(가) 움직입니다 (상속받은 메서드)
print(cat.speak())   # 야옹!

 

 

용어 정리

  • 부모 클래스(Parent Class) / 슈퍼 클래스(Super Class) / 베이스 클래스(Base Class): Animal
  • 자식 클래스(Child Class) / 서브 클래스(Sub Class) / 파생 클래스(Derived Class): Dog, Cat

 

 

 

 

2. super() 함수

`super()` 는 부모 클래스의 메서드를 호출할 때 사용한다.

 

 

super()로 부모 클래스 초기화

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    def introduce(self):
        return f"안녕하세요, {self.name}입니다. {self.age}살입니다."

class Student(Person):
    def __init__(self, name, age, student_id):
        super().__init__(name, age)  # 부모 클래스의 __init__ 호출
        self.student_id = student_id
    
    def introduce(self):
        parent_intro = super().introduce()  # 부모의 introduce() 호출
        return f"{parent_intro} 학번: {self.student_id}"

student = Student("김철수", 20, "2024001")
print(student.introduce())
# 안녕하세요, 김철수입니다. 20살입니다. 학번: 2024001

 

 

super() vs 직접 호출

class Parent:
    def method(self):
        return "부모 메서드"

class Child(Parent):
    def method(self):
        # 방법 1: super() 사용 (권장)
        return super().method() + " + 자식 메서드"
    
    def method_old_style(self):
        # 방법 2: 직접 호출 (비권장)
        return Parent.method(self) + " + 자식 메서드"

 

 

`super()` 를 사용하는 이유

  • 다중 상속에서 올바른 MRO를 따름
  • 코드 유지보수가 쉬움
  • 부모 클래스가 변경되어도 자식 클래스 수정 불필요

 

 

3. 메서드 오버라이딩 (Method Overriding)

자식 클래스가 부모 클래스의 메서드를 재정의하는 것이다.

class Shape:
    def __init__(self, color):
        self.color = color
    
    def area(self):
        raise NotImplementedError("서브클래스에서 구현해야 합니다")
    
    def describe(self):
        return f"{self.color} 도형"

class Circle(Shape):
    def __init__(self, color, radius):
        super().__init__(color)
        self.radius = radius
    
    def area(self):  # 오버라이딩
        return 3.14159 * self.radius ** 2
    
    def describe(self):  # 오버라이딩
        return f"{super().describe()}: 원 (반지름: {self.radius})"

class Rectangle(Shape):
    def __init__(self, color, width, height):
        super().__init__(color)
        self.width = width
        self.height = height
    
    def area(self):  # 오버라이딩
        return self.width * self.height
    
    def describe(self):
        return f"{super().describe()}: 직사각형 ({self.width}x{self.height})"

circle = Circle("빨강", 5)
rectangle = Rectangle("파랑", 4, 6)

print(circle.area())       # 78.53975
print(circle.describe())   # 빨강 도형: 원 (반지름: 5)
print(rectangle.area())    # 24
print(rectangle.describe()) # 파랑 도형: 직사각형 (4x6)

 

 

4. 다형성 (Polymorphism)

다형성은 같은 인터페이스로 다른 동작을 수행하는 것을 의미한다.

class Bird:
    def fly(self):
        return "새가 하늘을 납니다"

class Airplane:
    def fly(self):
        return "비행기가 하늘을 납니다"

class Butterfly:
    def fly(self):
        return "나비가 하늘을 날아다닙니다"

# 다형성: 같은 메서드 이름, 다른 동작
def make_it_fly(flying_object):
    print(flying_object.fly())

bird = Bird()
plane = Airplane()
butterfly = Butterfly()

make_it_fly(bird)       # 새가 하늘을 납니다
make_it_fly(plane)      # 비행기가 하늘을 납니다
make_it_fly(butterfly)  # 나비가 하늘을 날아다닙니다

 

 

덕 타이핑 (Duck Typing): Python은 "오리처럼 걷고, 오리처럼 꽥꽥거리면, 그것은 오리다"라는 철학을 따른다. 타입보다는 동작이 중요하다.

# 상속 관계가 없어도 같은 메서드만 있으면 OK!
class Dog:
    def speak(self):
        return "멍멍!"

class Cat:
    def speak(self):
        return "야옹!"

class Robot:
    def speak(self):
        return "삐빅!"

def animal_sound(obj):
    print(obj.speak())  # 어떤 타입이든 speak()만 있으면 됨

animal_sound(Dog())    # 멍멍!
animal_sound(Cat())    # 야옹!
animal_sound(Robot())  # 삐빅!

 

 

5. 추상 클래스 (Abstract Base Class)

추상 클래스는 인스턴스를 생성할 수 없으며, 자식 클래스에서 반드시 구현해야 하는 메서드를 정의한다.

from abc import ABC, abstractmethod

class Vehicle(ABC):  # ABC를 상속
    def __init__(self, brand):
        self.brand = brand
    
    @abstractmethod
    def start_engine(self):
        """서브클래스에서 반드시 구현해야 함"""
        pass
    
    @abstractmethod
    def stop_engine(self):
        """서브클래스에서 반드시 구현해야 함"""
        pass
    
    def honk(self):
        return f"{self.brand} 경적: 빵빵!"

class Car(Vehicle):
    def start_engine(self):
        return f"{self.brand} 자동차 시동을 켭니다"
    
    def stop_engine(self):
        return f"{self.brand} 자동차 시동을 끕니다"

class Motorcycle(Vehicle):
    def start_engine(self):
        return f"{self.brand} 오토바이 시동을 켭니다"
    
    def stop_engine(self):
        return f"{self.brand} 오토바이 시동을 끕니다"

# vehicle = Vehicle("현대")  # 에러! 추상 클래스는 인스턴스 생성 불가
car = Car("현대")
motorcycle = Motorcycle("혼다")

print(car.start_engine())       # 현대 자동차 시동을 켭니다
print(car.honk())               # 현대 경적: 빵빵!
print(motorcycle.start_engine()) # 혼다 오토바이 시동을 켭니다

 

 

6. 다중 상속 (Multiple Inheritance)

Python은 여러 부모 클래스로부터 상속받을 수 있다.

class Flyable:
    def fly(self):
        return "날 수 있습니다"

class Swimmable:
    def swim(self):
        return "헤엄칠 수 있습니다"

class Duck(Flyable, Swimmable):
    def __init__(self, name):
        self.name = name
    
    def quack(self):
        return "꽥꽥!"

duck = Duck("도널드")
print(duck.fly())    # 날 수 있습니다
print(duck.swim())   # 헤엄칠 수 있습니다
print(duck.quack())  # 꽥꽥!

 

 

MRO (Method Resolution Order)

다중 상속에서 메서드 탐색 순서를 나타낸다.

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
print(D.mro())        # MRO 확인
# [<class '__main__.D'>, <class '__main__.B'>, 
#  <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

 

 

MRO 규칙

  1. 자식 클래스가 먼저
  2. 부모 클래스는 선언 순서대로
  3. 같은 부모는 한 번만
  4. 최상위는 항상 object

 

 

7. 실전 예제: 직원 관리 시스템

모든 개념을 활용한 완전한 예제다.

from abc import ABC, abstractmethod
from datetime import datetime

class Employee(ABC):
    """직원 추상 클래스"""
    
    employee_count = 0
    
    def __init__(self, name, employee_id):
        self.name = name
        self.employee_id = employee_id
        self.hire_date = datetime.now()
        Employee.employee_count += 1
    
    @abstractmethod
    def calculate_salary(self):
        """급여 계산 - 서브클래스에서 구현 필수"""
        pass
    
    @abstractmethod
    def get_role(self):
        """직책 반환 - 서브클래스에서 구현 필수"""
        pass
    
    def get_info(self):
        return f"[{self.employee_id}] {self.name} - {self.get_role()}"
    
    @classmethod
    def get_employee_count(cls):
        return cls.employee_count

class Developer(Employee):
    """개발자 클래스"""
    
    def __init__(self, name, employee_id, programming_languages, level):
        super().__init__(name, employee_id)
        self.programming_languages = programming_languages
        self.level = level  # junior, senior, lead
    
    def calculate_salary(self):
        base_salary = 3000000
        level_bonus = {
            'junior': 0,
            'senior': 1000000,
            'lead': 2000000
        }
        return base_salary + level_bonus.get(self.level, 0)
    
    def get_role(self):
        return f"{self.level.capitalize()} Developer"
    
    def write_code(self, language):
        if language in self.programming_languages:
            return f"{self.name}이(가) {language}로 코드를 작성합니다"
        return f"{self.name}은(는) {language}를 모릅니다"

class Designer(Employee):
    """디자이너 클래스"""
    
    def __init__(self, name, employee_id, specialty):
        super().__init__(name, employee_id)
        self.specialty = specialty  # UI/UX, Graphic, etc.
    
    def calculate_salary(self):
        return 3500000
    
    def get_role(self):
        return f"{self.specialty} Designer"
    
    def create_design(self, project):
        return f"{self.name}이(가) {project}의 디자인을 작성합니다"

class Manager(Employee):
    """매니저 클래스"""
    
    def __init__(self, name, employee_id, team_size):
        super().__init__(name, employee_id)
        self.team_size = team_size
        self.team_members = []
    
    def calculate_salary(self):
        base_salary = 5000000
        team_bonus = self.team_size * 100000
        return base_salary + team_bonus
    
    def get_role(self):
        return f"Manager (팀원 {self.team_size}명)"
    
    def add_team_member(self, employee):
        self.team_members.append(employee)
        self.team_size = len(self.team_members)
    
    def get_team_info(self):
        team_info = [f"\n{self.name}의 팀:"]
        for member in self.team_members:
            team_info.append(f"  - {member.get_info()}")
        return "\n".join(team_info)

# 다형성을 활용한 급여 계산
def print_salary_info(employees):
    print("=== 급여 명세서 ===")
    total = 0
    for emp in employees:
        salary = emp.calculate_salary()
        total += salary
        print(f"{emp.get_info()}: {salary:,}원")
    print(f"\n총 급여: {total:,}원")
    print(f"총 직원 수: {Employee.get_employee_count()}명")

# 사용 예시
dev1 = Developer("김개발", "DEV001", ["Python", "JavaScript"], "senior")
dev2 = Developer("이코드", "DEV002", ["Java", "Kotlin"], "junior")
designer = Designer("박디자인", "DES001", "UI/UX")
manager = Manager("최매니저", "MGR001", 0)

# 팀 구성
manager.add_team_member(dev1)
manager.add_team_member(dev2)
manager.add_team_member(designer)

# 직원 목록
employees = [dev1, dev2, designer, manager]

# 다형성: 모든 직원 타입에 동일한 함수 적용
print_salary_info(employees)

print(manager.get_team_info())

print(f"\n{dev1.write_code('Python')}")
print(designer.create_design("쇼핑몰 앱"))

 

 

 

출력

=== 급여 명세서 ===
[DEV001] 김개발 - Senior Developer: 4,000,000원
[DEV002] 이코드 - Junior Developer: 3,000,000원
[DES001] 박디자인 - UI/UX Designer: 3,500,000원
[MGR001] 최매니저 - Manager (팀원 3명): 5,300,000원

총 급여: 15,800,000원
총 직원 수: 4명

최매니저의 팀:
  - [DEV001] 김개발 - Senior Developer
  - [DEV002] 이코드 - Junior Developer
  - [DES001] 박디자인 - UI/UX Designer

김개발이(가) Python로 코드를 작성합니다
박디자인이(가) 쇼핑몰 앱의 디자인을 작성합니다

 

 

8. 상속 vs 컴포지션 (Composition)

항상 상속이 답은 아니다. "is-a" 관계일 때는 상속, "has-a" 관계일 때는 컴포지션을 사용한다.

# 상속 (is-a): 자동차는 차량이다
class Vehicle:
    pass

class Car(Vehicle):  # Car is a Vehicle
    pass

# 컴포지션 (has-a): 자동차는 엔진을 가지고 있다
class Engine:
    def start(self):
        return "엔진 시동"

class Car:
    def __init__(self):
        self.engine = Engine()  # Car has an Engine
    
    def start(self):
        return self.engine.start()

 

 

언제 컴포지션을 사용할까?

  • 상속 계층이 너무 깊어질 때
  • 여러 기능을 유연하게 조합해야 할 때
  • "has-a" 관계가 더 명확할 때

 

 

 

핵심 요약

개념 설명 언제 사용?
상속 부모 클래스의 기능을 물려받음 is-a 관계, 코드 재사용
오버라이딩 부모 메서드를 재정의 동작을 변경해야 할 때
super() 부모 클래스 메서드 호출 부모 기능 확장
다형성 같은 인터페이스, 다른 동작 유연한 코드 설계
추상 클래스 구현을 강제하는 템플릿 인터페이스 정의
다중 상속 여러 부모로부터 상속 여러 기능 조합
컴포지션 객체를 포함 has-a 관계

 

 

 


 

 

 

 

 

결론

상속과 다형성을 잘 활용하면

  • 코드 재사용성이 높아진다
  • 유지보수가 쉬워진다
  • 확장 가능한 구조를 만들 수 있다
  • 더 직관적이고 읽기 쉬운 코드를 작성할 수 있다

 

 

하지만 과도한 상속은 복잡도를 높일 수 있으므로, 상황에 맞게 상속과 컴포지션을 적절히 선택하는 것이 중요하다!

 

 

 

 

 

 

 

 

 

 


Reference

728x90
반응형