파이썬 공부
들어가며
지난 글에서는 객체 지향 프로그래밍의 핵심인 클래스(Class)와 객체(Object)에 대해 배웠습니다. 클래스라는 ‘설계도’를 통해, 서로 연관된 데이터(속성)와 행동(메소드)을 하나의 덩어리(객체)로 묶어 관리하는 방법을 익혔습니다. 이로써 우리는 프로그램을 더 체계적이고 논리적으로 구성할 수 있는 강력한 도구를 손에 넣었습니다.
이제 우리가 게임을 만든다고 상상해 봅시다. 지난번에 만든 Character 클래스는 모든 캐릭터의 기본이 됩니다. 그런데 ‘전사’는 강한 체력과 방어 기술을, ‘마법사’는 마나와 강력한 마법 기술을 가져야 합니다. 이들을 표현하기 위해 각각 Warrior 클래스와 Mage 클래스를 새로 만들어야 할까요? 그렇게 한다면 Character 클래스에 이미 정의해 둔 name, hp와 같은 속성이나 show_status 같은 메소드를 또다시 반복해서 작성해야 할 것입니다. 이것은 매우 비효율적입니다.
이러한 문제, 즉 코드의 중복을 피하고 기존 코드를 효율적으로 재사용하기 위해 객체 지향 프로그래밍은 상속(Inheritance)이라는 눈부신 해결책을 제공합니다.
이번 글에서는 기존 클래스의 능력을 그대로 물려받아 새로운 클래스를 만드는 기법인 상속에 대해 알아보겠습니다.
상속이란 무엇일까?
상속은 이름 그대로, 현실 세계의 ‘상속’과 개념이 매우 유사합니다. 자식이 부모의 유전적 특징을 물려받는 것처럼, 프로그래밍에서의 상속은 한 클래스(자식)가 다른 클래스(부모)의 모든 속성과 메소드를 그대로 물려받는 것을 의미합니다.
- 부모 클래스 (Parent Class): 능력을 물려주는 클래스. 슈퍼클래스(Superclass) 또는 기반 클래스(Base Class)라고도 부릅니다.
- 자식 클래스 (Child Class): 능력을 물려받는 클래스. 서브클래스(Subclass) 또는 파생 클래스(Derived Class)라고도 부릅니다.
상속을 이용하면, 자식 클래스는 부모 클래스의 기능을 처음부터 다시 만들 필요 없이 그대로 사용하면서, 자신만의 새로운 기능을 추가하거나 기존 기능을 자신에게 맞게 변경할 수 있습니다.
파이썬에서 상속을 구현하는 방법은 매우 간단합니다. 클래스를 정의할 때, 이름 뒤의 괄호 () 안에 부모 클래스의 이름을 적어주기만 하면 됩니다.
class 자식클래스이름(부모클래스이름):
상속 구현해보기
지난번에 만들었던 Character 클래스를 부모로 삼아, 새로운 Warrior 클래스를 만들어 보겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# --- 부모 클래스 ---
class Character:
def __init__(self, name, hp, power):
self.name = name
self.hp = hp
self.power = power
def attack(self, target):
print(f"{self.name}이(가) {target.name}을(를) 공격합니다!")
target.hp -= self.power
def show_status(self):
print(f"이름: {self.name}, HP: {self.hp}")
# --- 자식 클래스 ---
# Character 클래스의 모든 것을 상속받습니다.
class Warrior(Character):
pass # 아직 아무 기능도 추가하지 않음
# Warrior 객체 생성
sword_man = Warrior('검사', 200, 20)
# Warrior 클래스에는 아무 코드도 없지만,
# 부모인 Character의 메소드를 사용할 수 있습니다.
sword_man.show_status() # 출력: 이름: 검사, HP: 200
sword_man.attack(Character('슬라임', 50, 5))
놀랍게도 Warrior 클래스 안에는 pass 외에 아무 코드도 없지만, Character 클래스의 __init__, show_status, attack 메소드를 모두 자신의 것처럼 완벽하게 사용할 수 있습니다. 이것이 바로 상속의 힘입니다.
기능 확장과 재정의 (오버라이딩)
상속의 진정한 가치는 단순히 기능을 물려받는 것에서 그치지 않고, 물려받은 기능을 자식 클래스의 상황에 맞게 확장하거나 변경하는 데 있습니다.
1. 메소드 오버라이딩 (Method Overriding)
메소드 오버라이딩(재정의)은 부모로부터 물려받은 메소드를 자식 클래스에서 똑같은 이름으로 다시 만드는 것입니다. 이렇게 하면, 자식 클래스의 객체는 부모의 메소드 대신 자신이 새롭게 정의한 메소드를 사용하게 됩니다.
Warrior 클래스의 공격 방식을 좀 더 특색있게 바꿔보겠습니다.
1
2
3
4
5
6
7
8
9
10
11
class Warrior(Character):
# 부모의 attack 메소드를 Warrior에 맞게 재정의(오버라이딩)합니다.
def attack(self, target):
print(f"{self.name}이(가) 검으로 {target.name}을(를) 강하게 내리칩니다!")
target.hp -= self.power * 1.2 # 공격력을 1.2배로 강화
sword_man = Warrior('검사', 200, 20)
slime = Character('슬라임', 50, 5)
sword_man.attack(slime) # Warrior에 새로 정의된 attack 메소드가 호출됩니다.
slime.show_status() # 출력: 이름: 슬라임, HP: 26 (50 - 20 * 1.2)
2. 부모 메소드 호출하기: super()
메소드를 오버라이딩할 때, 부모의 원래 기능을 완전히 무시하는 것이 아니라, 부모의 기능은 그대로 실행하면서 거기에 새로운 기능을 덧붙이고 싶을 때가 있습니다. 이때 super() 함수를 사용하면 자식 클래스 안에서 부모 클래스의 메소드를 호출할 수 있습니다.
Warrior가 defense라는 고유한 속성을 추가로 가지도록 __init__을 확장해 보겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Warrior(Character):
def __init__(self, name, hp, power, defense):
# super()를 이용해 부모 클래스(Character)의 __init__ 메소드를 먼저 호출합니다.
# name, hp, power 초기화는 부모에게 맡깁니다.
super().__init__(name, hp, power)
# Warrior만의 고유 속성을 추가로 초기화합니다.
self.defense = defense
print(f"방어력 {self.defense}을(를) 가진 전사가 생성되었습니다.")
def show_status(self):
# 부모의 show_status를 먼저 실행하여 이름과 HP를 출력하고,
super().show_status()
# 그 다음에 Warrior만의 정보를 추가로 출력합니다.
print(f"방어력: {self.defense}")
sword_man = Warrior('검사', 200, 20, 50)
sword_man.show_status()
super().__init__()를 통해 Character의 초기화 코드를 재사용함으로써 코드의 중복을 피하고, Warrior에 필요한 defense 속성만 추가로 처리할 수 있습니다. 이처럼 super()는 기존 기능을 확장하는 매우 세련되고 효율적인 방법을 제공합니다.
마무리하며
이번 글에서는 객체 지향 프로그래밍의 핵심 기둥 중 하나인 상속에 대해 배웠습니다. 상속을 통해 부모 클래스의 속성과 메소드를 자식 클래스가 물려받을 수 있으며, 이는 코드의 재사용성을 극적으로 높여줍니다.
또한, 메소드 오버라이딩을 통해 자식 클래스의 상황에 맞게 기능을 재정의할 수 있고, super() 함수를 이용해 부모의 기능을 재사용하면서 새로운 기능을 확장하는 방법도 익혔습니다.
이제 우리는 클래스라는 설계도를 만들고, 상속을 통해 이 설계도를 확장하며 체계적으로 프로그램을 구조화할 수 있게 되었습니다. 지금까지 파이썬의 핵심적인 개념은 대부분 다루었습니다. 기본적인 문법부터 시작해 함수, 모듈, 클래스, 그리고 예외 처리에 이르기까지, 이제 여러분은 파이썬으로 웬만한 프로그램을 만들 수 있는 기초 체력을 모두 갖춘 셈입니다.
다음 글부터는 파이썬의 내장 기능과 표준 라이브러리를 좀 더 깊이 있게 탐험하며, 실제 프로그래밍에서 유용하게 쓰이는 다양한 도구들과 고급 기법들을 알아보는 시간을 갖도록 하겠습니다. 그 첫 번째 순서로, 파이썬의 기본 데이터 타입들을 훨씬 더 다채롭게 다룰 수 있게 해주는 유용한 내장 함수들에 대해 살펴보겠습니다.