본문 바로가기
Python

27. 클래스와 객체 (2) - 상속과 다형성

by 샤나엘 2026. 2. 22.
반응형

클래스와 객체 (2) - 상속과 다형성

27. 클래스와 객체 (2) - 상속과 다형성

기존 클래스를 확장해서 새 클래스를 만들 수 있다면? 코드 재사용의 끝판왕, 상속(Inheritance)다형성(Polymorphism)을 알아보자. 객체지향의 진짜 힘은 여기서 나온다.


상속이란?

상속은 기존 클래스(부모)의 속성과 메서드를 물려받아 새 클래스(자식)를 만드는 것이다.

class Animal:          # 부모 클래스 (기반 클래스)
    def __init__(self, name):
        self.name = name

    def speak(self):
        print(f"{self.name}이(가) 소리를 냅니다.")

class Dog(Animal):     # 자식 클래스 (Animal을 상속)
    def speak(self):
        print(f"{self.name}: 멍멍!")

class Cat(Animal):     # 자식 클래스 (Animal을 상속)
    def speak(self):
        print(f"{self.name}: 야옹!")

dog = Dog("멍멍이")
cat = Cat("나비")

dog.speak()   # 멍멍이: 멍멍!
cat.speak()   # 나비: 야옹!

DogCatAnimal로부터 name 속성과 __init__ 메서드를 물려받았다. speak() 메서드만 각자 다르게 정의했다.

 

상속 다이어그램


상속의 기본 문법

class 부모클래스:
    # 부모의 속성과 메서드

class 자식클래스(부모클래스):     # 괄호 안에 부모 지정
    # 자식만의 속성과 메서드 추가

자식 클래스에 기능 추가

class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def info(self):
        print(f"{self.name} ({self.age}살)")

class Dog(Animal):
    def fetch(self):               # Dog만의 메서드
        print(f"{self.name}이(가) 공을 물어옵니다!")

class Cat(Animal):
    def purr(self):                # Cat만의 메서드
        print(f"{self.name}이(가) 골골거립니다~")

dog = Dog("멍멍이", 3)
dog.info()     # 멍멍이 (3살) — 부모의 메서드 사용
dog.fetch()    # 멍멍이이(가) 공을 물어옵니다!

cat = Cat("나비", 2)
cat.info()     # 나비 (2살) — 부모의 메서드 사용
cat.purr()     # 나비이(가) 골골거립니다~

super() - 부모의 메서드 호출

자식 클래스에서 __init__을 재정의할 때, 부모의 초기화도 실행해야 한다. super()를 쓴다.

class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age

class Dog(Animal):
    def __init__(self, name, age, breed):
        super().__init__(name, age)   # 부모의 __init__ 호출
        self.breed = breed            # Dog만의 속성 추가

    def info(self):
        print(f"{self.name} ({self.breed}, {self.age}살)")

dog = Dog("멍멍이", 3, "골든리트리버")
dog.info()   # 멍멍이 (골든리트리버, 3살)

super()를 안 쓰면?

class Dog(Animal):
    def __init__(self, name, age, breed):
        # super().__init__(name, age)를 빼먹으면...
        self.breed = breed

dog = Dog("멍멍이", 3, "골든리트리버")
print(dog.breed)   # 골든리트리버
print(dog.name)    # AttributeError! name 속성이 없다!

super().__init__()을 호출하지 않으면 부모의 초기화가 실행되지 않아 부모의 속성이 생성되지 않는다.


메서드 오버라이딩 (Override)

부모의 메서드를 자식에서 같은 이름으로 재정의하는 것을 오버라이딩이라 한다.

class Shape:
    def __init__(self, color="검정"):
        self.color = color

    def area(self):
        return 0

    def describe(self):
        print(f"{self.color} 도형, 넓이: {self.area()}")

class Circle(Shape):
    def __init__(self, radius, color="빨강"):
        super().__init__(color)
        self.radius = radius

    def area(self):                  # 오버라이딩
        return 3.14159 * self.radius ** 2

class Rectangle(Shape):
    def __init__(self, width, height, color="파랑"):
        super().__init__(color)
        self.width = width
        self.height = height

    def area(self):                  # 오버라이딩
        return self.width * self.height

# 사용
c = Circle(5)
r = Rectangle(4, 6)

c.describe()   # 빨강 도형, 넓이: 78.53975
r.describe()   # 파랑 도형, 넓이: 24

describe() 메서드는 부모에 한 번만 정의했지만, area()가 각 자식에서 오버라이딩 되어 다르게 동작한다.


다형성 (Polymorphism)

다형성은 같은 메서드 이름이 객체에 따라 다르게 동작하는 것이다.

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        pass

class Dog(Animal):
    def speak(self):
        return f"{self.name}: 멍멍!"

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

class Duck(Animal):
    def speak(self):
        return f"{self.name}: 꽥꽥!"

# 다형성의 힘: 같은 코드로 다른 결과
animals = [Dog("멍멍이"), Cat("나비"), Duck("도날드")]

for animal in animals:
    print(animal.speak())   # 각 동물에 맞는 소리!

출력:

멍멍이: 멍멍!
나비: 야옹!
도날드: 꽥꽥!

핵심은 animal.speak()라는 동일한 코드가 객체의 타입에 따라 다르게 동작한다는 것이다.


isinstance() - 타입 확인

isinstance()로 객체가 특정 클래스의 인스턴스인지 확인할 수 있다.

dog = Dog("멍멍이")
cat = Cat("나비")

print(isinstance(dog, Dog))      # True
print(isinstance(dog, Animal))   # True (부모 클래스도 True!)
print(isinstance(dog, Cat))      # False

print(isinstance(cat, Cat))      # True
print(isinstance(cat, Animal))   # True

실전 활용

def animal_hospital(animal):
    """동물 병원 접수"""
    if isinstance(animal, Dog):
        print(f"강아지 {animal.name} 접수 완료 (외과)")
    elif isinstance(animal, Cat):
        print(f"고양이 {animal.name} 접수 완료 (내과)")
    else:
        print(f"{animal.name} 접수 완료 (일반)")

animal_hospital(Dog("멍멍이"))   # 강아지 멍멍이 접수 완료 (외과)
animal_hospital(Cat("나비"))     # 고양이 나비 접수 완료 (내과)

다중 상속

파이썬은 여러 부모 클래스를 동시에 상속받을 수 있다. 하지만 복잡해지기 쉬우므로 주의가 필요하다.

class Flyable:
    def fly(self):
        print(f"{self.name}이(가) 날아갑니다!")

class Swimmable:
    def swim(self):
        print(f"{self.name}이(가) 수영합니다!")

class Duck(Animal, Flyable, Swimmable):
    def speak(self):
        return f"{self.name}: 꽥꽥!"

duck = Duck("도날드")
print(duck.speak())   # 도날드: 꽥꽥!
duck.fly()            # 도날드이(가) 날아갑니다!
duck.swim()           # 도날드이(가) 수영합니다!

MRO (Method Resolution Order)

다중 상속에서 메서드 검색 순서를 확인할 수 있다.

print(Duck.__mro__)
# (<class 'Duck'>, <class 'Animal'>, <class 'Flyable'>, <class 'Swimmable'>, <class 'object'>)

다중 상속은 강력하지만, 초보 단계에서는 단일 상속을 주로 사용하고, 다중 상속은 Mixin 패턴 정도로만 활용하는 것을 권장한다.


실전 예시: 직원 관리 시스템

class Employee:
    """기본 직원"""
    def __init__(self, name, employee_id, salary):
        self.name = name
        self.employee_id = employee_id
        self.salary = salary

    def get_pay(self):
        return self.salary

    def info(self):
        print(f"[{self.employee_id}] {self.name} - 급여: {self.get_pay():,}원")

class Manager(Employee):
    """관리자 — 기본급 + 보너스"""
    def __init__(self, name, employee_id, salary, bonus):
        super().__init__(name, employee_id, salary)
        self.bonus = bonus

    def get_pay(self):    # 오버라이딩
        return self.salary + self.bonus

class Intern(Employee):
    """인턴 — 기본급의 50%"""
    def get_pay(self):    # 오버라이딩
        return int(self.salary * 0.5)

# 직원 목록
employees = [
    Employee("김사원", "E001", 3000000),
    Manager("이부장", "M001", 5000000, 2000000),
    Intern("박인턴", "I001", 2400000),
]

print("=== 급여 명세서 ===")
total = 0
for emp in employees:
    emp.info()
    total += emp.get_pay()

print(f"\n총 급여 합계: {total:,}원")

출력:

=== 급여 명세서 ===
[E001] 김사원 - 급여: 3,000,000원
[M001] 이부장 - 급여: 7,000,000원
[I001] 박인턴 - 급여: 1,200,000원

총 급여 합계: 11,200,000원

직접 해보기

문제 1. Vehicle 부모 클래스와 Car, Bicycle 자식 클래스를 만들어보자. Vehiclenamespeed 속성을 가지고, 각 자식은 describe() 메서드를 오버라이딩한다.

문제 2. ShapeCircle, Rectangle, Triangle 상속 구조를 만들고, 각 도형의 넓이를 계산하는 area() 메서드를 오버라이딩해보자.

문제 3. 게임 캐릭터 시스템을 만들어보자. Character 부모 클래스(이름, HP)와 Warrior(공격력), Mage(마법력) 자식 클래스를 만들자. 각 클래스에 attack() 메서드를 다르게 구현하자.

문제 4. 동물 리스트를 만들고, for문으로 순회하면서 isinstance()를 사용해 동물별로 다른 메시지를 출력해보자.

정답 보기
# 문제 1 - Vehicle 상속
class Vehicle:
    def __init__(self, name, speed):
        self.name = name
        self.speed = speed
‍
    def describe(self):
        print(f"{self.name} (최대 {self.speed}km/h)")
‍
class Car(Vehicle):
    def __init__(self, name, speed, fuel):
        super().__init__(name, speed)
        self.fuel = fuel
‍
    def describe(self):
        print(f"자동차: {self.name} ({self.fuel}, {self.speed}km/h)")
‍
class Bicycle(Vehicle):
    def describe(self):
        print(f"자전거: {self.name} ({self.speed}km/h)")
‍
car = Car("소나타", 200, "가솔린")
bike = Bicycle("삼천리", 30)
car.describe()    # 자동차: 소나타 (가솔린, 200km/h)
bike.describe()   # 자전거: 삼천리 (30km/h)
‍
‍
# 문제 2 - Shape 상속
class Shape:
    def area(self):
        return 0
‍
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
‍
class Triangle(Shape):
    def __init__(self, base, height):
        self.base = base
        self.height = height
    def area(self):
        return self.base * self.height / 2
‍
shapes = [Circle(5), Rectangle(4, 6), Triangle(3, 8)]
for s in shapes:
    print(f"{s.__class__.__name__}: {s.area():.2f}")
‍
‍
# 문제 3 - 게임 캐릭터
class Character:
    def __init__(self, name, hp):
        self.name = name
        self.hp = hp
‍
    def attack(self):
        print(f"{self.name}이(가) 공격합니다!")
‍
class Warrior(Character):
    def __init__(self, name, hp, power):
        super().__init__(name, hp)
        self.power = power
‍
    def attack(self):
        print(f"[전사] {self.name}: 검으로 {self.power} 데미지!")
‍
class Mage(Character):
    def __init__(self, name, hp, magic):
        super().__init__(name, hp)
        self.magic = magic
‍
    def attack(self):
        print(f"[마법사] {self.name}: 마법으로 {self.magic} 데미지!")
‍
party = [Warrior("아서", 100, 25), Mage("멀린", 70, 40)]
for c in party:
    c.attack()
‍
‍
# 문제 4 - isinstance 활용
class Animal:
    def __init__(self, name):
        self.name = name
‍
class Dog(Animal): pass
class Cat(Animal): pass
class Bird(Animal): pass
‍
animals = [Dog("멍멍이"), Cat("나비"), Bird("짹짹이"), Dog("바둑이")]
‍
for a in animals:
    if isinstance(a, Dog):
        print(f"{a.name}: 산책 가자!")
    elif isinstance(a, Cat):
        print(f"{a.name}: 츄르 줄까?")
    elif isinstance(a, Bird):
        print(f"{a.name}: 하늘 높이 날아라!")

오늘의 정리

항목 내용
상속 class 자식(부모): — 부모의 속성·메서드를 물려받음
super() 부모의 메서드를 호출. super().__init__()으로 부모 초기화
오버라이딩 부모의 메서드를 자식에서 같은 이름으로 재정의
다형성 같은 메서드가 객체 타입에 따라 다르게 동작
isinstance() 객체의 타입 확인. 부모 클래스도 True
다중 상속 class 자식(부모1, 부모2): — 여러 부모 상속 가능

다음 편 예고: 매직 메서드 - 파이썬 객체의 비밀

__str__, __len__, __add__... 언더스코어 두 개로 감싸인 특별한 메서드들의 비밀을 파헤쳐보자!


태그: 파이썬 Python 파이썬독학 상속 inheritance 다형성 오버라이딩 super OOP 파이썬중급 IT교육

반응형

댓글