안녕하세요! 지난 시간에는 클래스 변수, 정적 메서드, 클래스 메서드와 같이 클래스 수준에서 데이터를 관리하고 행동을 정의하는 다양한 방법에 대해 알아보았습니다. 이제 여러분은 클래스를 더욱 세밀하게 설계하고 활용할 수 있게 되었을 거예요!
이번 시간에는 객체지향 프로그래밍(OOP)의 핵심 원칙 중 두 가지인 **상속(Inheritance)**과 **다형성(Polymorphism)**에 대해 자세히 알아보겠습니다. 이 두 가지 개념은 OOP의 강력함을 가장 잘 보여주는 요소이며, 코드 재사용성, 확장성, 그리고 유연성을 극대화하는 데 필수적입니다.
상속은 "기존 것을 물려받아 확장하는 것"이고, 다형성은 "하나의 형태가 여러 다른 동작을 하는 것"이라고 비유할 수 있습니다. 그럼, 이 두 가지 개념이 어떻게 작동하는지 함께 살펴볼까요?
Part 1: 상속 (Inheritance) - 코드 재사용 및 확장
상속은 한 클래스(자식 클래스 또는 파생 클래스)가 다른 클래스(부모 클래스 또는 기반 클래스)의 속성(변수)과 행동(메서드)을 물려받아 사용하는 OOP의 핵심 기능입니다. 이를 통해 코드의 중복을 줄이고, 계층적인 관계를 설정하여 프로그램을 더욱 체계적으로 만들 수 있습니다.
1. 상속의 개념 및 장점
- 코드 재사용: 부모 클래스에 정의된 속성과 메서드를 자식 클래스에서 별도로 작성하지 않고 그대로 사용할 수 있습니다.
- 확장성: 부모 클래스의 기능을 그대로 물려받으면서, 자식 클래스에서 새로운 기능을 추가하거나, 물려받은 기능을 변경(오버라이딩)하여 확장할 수 있습니다.
- 계층 구조: 현실 세계의 "IS-A" 관계를 모델링하는 데 유용합니다. (예: "강아지는 동물이다", "자동차는 운송수단이다")
2. 상속 문법
class 자식클래스(부모클래스):
# 부모 클래스의 속성/메서드를 물려받음
# 필요한 경우 새로운 속성/메서드 추가 또는 부모 메서드 오버라이딩
3. super().__init__() - 부모 클래스 생성자 호출
- 자식 클래스에 __init__ 생성자를 정의할 경우, 부모 클래스의 __init__도 호출하여 부모 클래스의 속성들이 제대로 초기화되도록 해야 합니다. 이때 super().__init__()를 사용합니다.
예시: Animal 클래스와 Dog, Cat 클래스 상속
# 파일 이름: inheritance_example.py
# 부모 클래스 정의
class Animal:
def __init__(self, name, age):
self.name = name
self.age = age
print(f"{self.name} (동물)이 생성되었습니다.")
def speak(self):
print(f"{self.name}이(가) 소리를 냅니다.")
def eat(self):
print(f"{self.name}이(가) 먹이를 먹습니다.")
# 자식 클래스 Dog 정의 (Animal을 상속받음)
class Dog(Animal): # Animal을 상속받는다고 명시
def __init__(self, name, age, breed):
super().__init__(name, age) # 부모 클래스 Animal의 __init__ 호출
self.breed = breed # Dog 고유의 속성 추가
print(f"견종 {self.breed}인 {self.name} (강아지)가 생성되었습니다.")
# 부모의 speak 메서드를 오버라이딩 (재정의)
def speak(self):
print(f"{self.name}이(가) 멍멍 짖습니다.")
def fetch(self): # Dog 고유의 메서드 추가
print(f"{self.name}이(가) 공을 가져옵니다.")
# 자식 클래스 Cat 정의 (Animal을 상속받음)
class Cat(Animal): # Animal을 상속받는다고 명시
def __init__(self, name, age, color):
super().__init__(name, age) # 부모 클래스 Animal의 __init__ 호출
self.color = color # Cat 고유의 속성 추가
print(f"색상 {self.color}인 {self.name} (고양이)가 생성되었습니다.")
# 부모의 speak 메서드를 오버라이딩 (재정의)
def speak(self):
print(f"{self.name}이(가) 야옹하고 웁니다.")
# 객체 생성 및 메서드 호출
my_dog = Dog("바둑이", 3, "진돗개")
my_cat = Cat("나비", 2, "갈색")
print("\n")
# 부모로부터 물려받은 메서드 사용
my_dog.eat() # Animal의 eat() 메서드 사용
my_cat.eat() # Animal의 eat() 메서드 사용
# 오버라이딩된 메서드 사용
my_dog.speak() # Dog의 speak() 메서드 사용
my_cat.speak() # Cat의 speak() 메서드 사용
# 자식 클래스 고유의 메서드 사용
my_dog.fetch()
# my_cat.fetch() # Cat은 fetch 메서드가 없으므로 AttributeError 발생
[VS Code 터미널 출력]

Part 2: 다형성 (Polymorphism) - 다양한 형태의 동일한 동작
다형성은 객체지향 프로그래밍에서 매우 중요한 개념으로, "하나의 인터페이스(동작 방식)가 여러 다른 형태의 객체에 적용될 수 있는 능력"을 의미합니다. 즉, 서로 다른 클래스의 객체들이 동일한 메서드 이름으로 호출되었을 때, 각 객체의 타입에 맞게 고유한 방식으로 동작하는 것을 말합니다.
1. 다형성의 개념 및 장점
- 유연성 및 확장성: 새로운 클래스를 추가하더라도 기존 코드를 변경할 필요 없이 새로운 객체를 동일한 방식으로 다룰 수 있습니다.
- 코드의 간결성: 여러 타입의 객체를 개별적으로 처리할 필요 없이, 공통된 인터페이스(메서드 이름)를 통해 일괄적으로 처리할 수 있습니다.
2. 다형성 구현 방법 - 메서드 오버라이딩(Overriding)
다형성은 주로 메서드 오버라이딩을 통해 구현됩니다. 오버라이딩은 자식 클래스에서 부모 클래스의 메서드와 동일한 이름으로 메서드를 재정의하는 것을 의미합니다. (위 상속 예시에서 Dog와 Cat이 speak() 메서드를 재정의한 것이 바로 오버라이딩입니다.)
예시: 다형성 활용 (동물들의 소리 내기)
위에서 정의한 Animal, Dog, Cat 클래스를 사용하여 다형성을 보여주겠습니다. 각 객체는 speak()라는 동일한 메서드를 가지고 있지만, 호출하면 각자의 방식으로 소리를 냅니다.
# 파일 이름: polymorphism_example.py
# 이전에 정의한 Animal, Dog, Cat 클래스 코드를 여기에 그대로 붙여넣습니다.
# ----------------------------------------------------------------------
class Animal:
def __init__(self, name, age):
self.name = name
self.age = age
# print(f"{self.name} (동물)이 생성되었습니다.") # 생성 메시지는 여기서는 생략
def speak(self):
print(f"{self.name}이(가) 소리를 냅니다.")
def eat(self):
print(f"{self.name}이(가) 먹이를 먹습니다.")
class Dog(Animal):
def __init__(self, name, age, breed):
super().__init__(name, age)
self.breed = breed
# print(f"견종 {self.breed}인 {self.name} (강아지)가 생성되었습니다.") # 생성 메시지 생략
def speak(self):
print(f"{self.name}이(가) 멍멍 짖습니다.")
def fetch(self):
print(f"{self.name}이(가) 공을 가져옵니다.")
class Cat(Animal):
def __init__(self, name, age, color):
super().__init__(name, age)
self.color = color
# print(f"색상 {self.color}인 {self.name} (고양이)가 생성되었습니다.") # 생성 메시지 생략
def speak(self):
print(f"{self.name}이(가) 야옹하고 웁니다.")
# ----------------------------------------------------------------------
# 다양한 타입의 동물 객체들을 리스트에 담기
animals = [
Animal("동물", 5), # 부모 클래스 객체
Dog("강아지", 3, "치와와"), # 자식 클래스 Dog 객체
Cat("고양이", 2, "검은색") # 자식 클래스 Cat 객체
]
print("--- 모든 동물들이 소리를 내는 중 ---")
# 리스트에 있는 모든 객체에 대해 동일하게 speak() 메서드 호출
for animal in animals:
animal.speak() # 각 객체의 실제 타입에 맞는 speak() 메서드가 호출됨
print("\n--- 모든 동물들이 먹이를 먹는 중 ---")
for animal in animals:
animal.eat() # Animal의 eat() 메서드 (오버라이딩 안됨)
- animals 리스트에는 Animal, Dog, Cat이라는 서로 다른 타입의 객체들이 섞여 있습니다.
- for animal in animals: 반복문 안에서 animal.speak()를 호출하면, 각 animal 객체의 실제 타입(Dog인지, Cat인지)에 따라 오버라이딩된 speak() 메서드가 자동으로 실행됩니다. 이것이 바로 다형성입니다.
- animal.eat()의 경우, eat() 메서드는 자식 클래스에서 오버라이딩되지 않았으므로 부모 클래스(Animal)의 eat() 메서드가 호출됩니다.
[VS Code 터미널 출력]

Part 3: 상속과 다형성의 관계 및 중요성
상속과 다형성은 객체지향 프로그래밍에서 서로 밀접하게 연결되어 함께 사용될 때 강력한 시너지를 발휘합니다.
- 관계:
- 다형성은 종종 상속 관계가 있을 때 가장 효과적으로 나타납니다. 부모 클래스가 공통된 인터페이스(메서드 이름)를 제공하고, 자식 클래스들이 이를 상속받아 각자에게 맞는 방식으로 오버라이딩함으로써 다형성이 구현됩니다.
- "IS-A" 관계를 통해 부모 타입의 변수에 자식 타입의 객체를 할당할 수 있기 때문에, 다형적인 처리가 가능해집니다. (예: animal = Dog(), animal = Cat())
- 중요성:
- 확장성 (Extensibility): 새로운 종류의 동물을 추가하더라도(Bird 클래스), animals 리스트에 Bird 객체를 추가하고 animal.speak()를 호출하는 기존 for 루프는 아무런 수정 없이 잘 작동합니다. (개방-폐쇄 원칙)
- 유지보수성 (Maintainability): 코드가 변경되거나 확장될 때, 영향을 받는 부분이 최소화됩니다.
- 재사용성 (Reusability): 공통적인 기능은 부모 클래스에 정의하고, 자식 클래스에서는 필요한 부분만 재정의하므로 코드 재사용성이 높아집니다.
- 설계의 유연성: 프로그램의 구조가 더욱 유연해지고, 복잡한 시스템을 관리하기 쉬워집니다.
마무리하며
이번 시간에는 객체지향 프로그래밍의 핵심 기둥인 **상속(Inheritance)**과 **다형성(Polymorphism)**에 대해 자세히 알아보았습니다.
- 상속: 부모 클래스의 속성과 메서드를 물려받아 코드를 재사용하고 확장하는 기능.
- 다형성: 서로 다른 타입의 객체들이 동일한 메서드 이름에 대해 각자의 방식으로 다르게 반응하는 능력. 주로 메서드 오버라이딩을 통해 구현됩니다.
이 두 가지 개념은 프로그램을 견고하고 유연하며 재사용 가능한 형태로 만드는 데 매우 중요합니다. 현실 세계를 프로그래밍으로 모델링할 때 자주 사용되는 강력한 도구이니, 충분히 연습하여 익숙해지는 것이 중요합니다.
다음 포스팅에서는 객체지향 프로그래밍의 또 다른 중요한 원칙인 **오버라이딩(Overriding)**과 super() 함수의 활용에 대해 더 깊이 알아보겠습니다.
궁금한 점이 있다면 언제든지 질문해주세요! 다음 포스팅에서 만나요!
'Python' 카테고리의 다른 글
6-7. 캡슐화와 접근 제한자 개념 (0) | 2025.07.02 |
---|---|
6-6. 오버라이딩과 super() (0) | 2025.07.01 |
6-2. 생성자(__init__)와 인스턴스 변수 (0) | 2025.06.29 |
5-5. raise와 사용자 정의 예외 (0) | 2025.06.29 |
5-4. 예외 처리 try-except 구문 (0) | 2025.06.28 |