[Python 100일 챌린지] Day 35 - 상속의 기초
“강아지”와 “고양이” 클래스를 만드는데, 둘 다 eat(), sleep() 메서드가 똑같다면? 중복 코드를 계속 작성해야 할까요? 🤔 ──아니요! “동물(Animal)” 클래스를 만들고 공통 기능을 물려받으면 됩니다! 😊
게임 캐릭터를 생각해보세요. 전사, 마법사, 궁수 모두 이동(), 공격() 같은 기본 기능은 같고, 특수기술만 다르죠. 매번 이동()을 다시 작성할 필요 없습니다!
스마트폰도 마찬가지입니다. 갤럭시S, 갤럭시노트, 갤럭시폴드 모두 “갤럭시” 시리즈의 기본 기능을 물려받고, 각자 고유 기능만 추가합니다.
이것이 상속(Inheritance)입니다! 코드 중복을 없애고 재사용성을 극대화하는 OOP의 핵심 원리! 💡
🎯 오늘의 학습 목표
⭐⭐⭐ (30-40분 완독)
📚 사전 지식
🎯 학습 목표 1: 상속의 개념과 필요성 이해하기
상속이란?
상속(Inheritance)은 기존 클래스의 속성과 메서드를 물려받아 새로운 클래스를 만드는 것입니다.
🏠 실생활 비유: 부모와 자식
상속의 개념: 부모의 기능을 물려받고 새로운 기능을 추가합니다.
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 Animal:
def eat(self):
return "먹는다"
def sleep(self):
return "잔다"
# 자식 (개) - 동물의 모든 것을 물려받음 + 고유 기능 추가
class Dog(Animal):
def bark(self):
return "멍멍!"
# 자식 (고양이) - 동물의 모든 것을 물려받음 + 고유 기능 추가
class Cat(Animal):
def meow(self):
return "야옹~"
dog = Dog()
print(dog.eat()) # 먹는다 (부모에게서 물려받음)
print(dog.sleep()) # 잔다 (부모에게서 물려받음)
print(dog.bark()) # 멍멍! (자신만의 기능)
cat = Cat()
print(cat.eat()) # 먹는다
print(cat.meow()) # 야옹~
🎯 학습 목표 2: 부모 클래스와 자식 클래스 정의하기
기본 상속 문법
1
2
3
4
5
6
7
class ParentClass:
"""부모 클래스"""
pass
class ChildClass(ParentClass):
"""자식 클래스"""
pass
예제 1: 간단한 상속
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
27
28
29
30
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):
"""학생 클래스 (자식) - Person을 상속"""
def __init__(self, name, age, student_id):
# 부모 클래스 초기화
super().__init__(name, age)
# 자식 클래스 고유 속성
self.student_id = student_id
def study(self):
return f"{self.name}이(가) 공부합니다."
# 사용
person = Person("홍길동", 30)
print(person.introduce())
student = Student("김철수", 20, "2024001")
print(student.introduce()) # 부모 메서드
print(student.study()) # 자식 메서드
print(f"학번: {student.student_id}")
실행 결과:
1
2
3
4
안녕하세요, 홍길동입니다. 30살입니다.
안녕하세요, 김철수입니다. 20살입니다.
김철수이(가) 공부합니다.
학번: 2024001
🎯 학습 목표 3: super()로 부모 클래스 접근하기
super()는 부모 클래스의 메서드를 호출할 때 사용합니다.
왜 super()를 사용할까?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Parent:
def __init__(self, name):
self.name = name
print(f"Parent 초기화: {name}")
class Child(Parent):
def __init__(self, name, age):
# ❌ 방법 1: 직접 호출 (권장하지 않음)
# Parent.__init__(self, name)
# ✅ 방법 2: super() 사용 (권장)
super().__init__(name)
self.age = age
print(f"Child 초기화: {age}살")
child = Child("홍길동", 10)
실행 결과:
1
2
Parent 초기화: 홍길동
Child 초기화: 10살
실전 예제: 직원 관리 시스템
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class Employee:
"""직원 클래스 (부모)"""
employee_count = 0
def __init__(self, name, employee_id, salary):
self.name = name
self.employee_id = employee_id
self.salary = salary
Employee.employee_count += 1
def get_info(self):
return f"{self.name} (ID: {self.employee_id})"
def get_annual_salary(self):
return self.salary * 12
class Developer(Employee):
"""개발자 클래스 (자식)"""
def __init__(self, name, employee_id, salary, language):
super().__init__(name, employee_id, salary)
self.language = language
def write_code(self):
return f"{self.name}이(가) {self.language}로 코딩합니다."
def get_info(self):
# 부모 메서드 활용 + 추가 정보
base_info = super().get_info()
return f"{base_info} - {self.language} 개발자"
class Designer(Employee):
"""디자이너 클래스 (자식)"""
def __init__(self, name, employee_id, salary, tool):
super().__init__(name, employee_id, salary)
self.tool = tool
def design(self):
return f"{self.name}이(가) {self.tool}로 디자인합니다."
def get_info(self):
base_info = super().get_info()
return f"{base_info} - {self.tool} 디자이너"
# 사용
dev = Developer("홍길동", "D001", 5000000, "Python")
designer = Designer("김철수", "D002", 4500000, "Figma")
print(dev.get_info())
print(dev.write_code())
print(f"연봉: {dev.get_annual_salary():,}원\n")
print(designer.get_info())
print(designer.design())
print(f"연봉: {designer.get_annual_salary():,}원\n")
print(f"총 직원 수: {Employee.employee_count}명")
실행 결과:
1
2
3
4
5
6
7
8
9
홍길동 (ID: D001) - Python 개발자
홍길동이(가) Python로 코딩합니다.
연봉: 60,000,000원
김철수 (ID: D002) - Figma 디자이너
김철수이(가) Figma로 디자인합니다.
연봉: 54,000,000원
총 직원 수: 2명
🎯 학습 목표 4: 메서드 오버라이딩 이해하기
메서드 오버라이딩이란?
자식 클래스에서 부모 메서드를 재정의하는 것입니다.
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
27
28
29
30
31
32
33
34
35
36
class Animal:
"""동물 클래스"""
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name}이(가) 소리를 냅니다."
class Dog(Animal):
"""개 클래스 - speak 오버라이딩"""
def speak(self):
return f"{self.name}: 멍멍!"
class Cat(Animal):
"""고양이 클래스 - speak 오버라이딩"""
def speak(self):
return f"{self.name}: 야옹~"
class Cow(Animal):
"""소 클래스 - speak 오버라이딩"""
def speak(self):
return f"{self.name}: 음메~"
# 사용
animals = [
Dog("바둑이"),
Cat("나비"),
Cow("얼룩이")
]
for animal in animals:
print(animal.speak())
실행 결과:
1
2
3
바둑이: 멍멍!
나비: 야옹~
얼룩이: 음메~
🚗 실전 예제: 차량 관리 시스템
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
class Vehicle:
"""차량 클래스 (부모)"""
def __init__(self, brand, model, year):
self.brand = brand
self.model = model
self.year = year
self.mileage = 0
def drive(self, distance):
self.mileage += distance
return f"{distance}km 주행 완료 (총 주행거리: {self.mileage}km)"
def get_info(self):
return f"{self.year}년식 {self.brand} {self.model}"
def honk(self):
return "빵빵!"
class Car(Vehicle):
"""자동차 클래스"""
def __init__(self, brand, model, year, num_doors):
super().__init__(brand, model, year)
self.num_doors = num_doors
def get_info(self):
base_info = super().get_info()
return f"{base_info} ({self.num_doors}도어)"
class Truck(Vehicle):
"""트럭 클래스"""
def __init__(self, brand, model, year, cargo_capacity):
super().__init__(brand, model, year)
self.cargo_capacity = cargo_capacity
def get_info(self):
base_info = super().get_info()
return f"{base_info} (적재량: {self.cargo_capacity}톤)"
def load_cargo(self, weight):
if weight <= self.cargo_capacity:
return f"✅ {weight}톤 적재 완료"
else:
return f"❌ 적재 불가 (최대: {self.cargo_capacity}톤)"
class Motorcycle(Vehicle):
"""오토바이 클래스"""
def __init__(self, brand, model, year, engine_cc):
super().__init__(brand, model, year)
self.engine_cc = engine_cc
def get_info(self):
base_info = super().get_info()
return f"{base_info} ({self.engine_cc}cc)"
def honk(self):
return "빠아앙!" # 오버라이딩
# 사용
car = Car("현대", "소나타", 2024, 4)
truck = Truck("볼보", "FH16", 2023, 20)
bike = Motorcycle("혼다", "CB500X", 2024, 500)
vehicles = [car, truck, bike]
print("="*50)
print("차량 정보")
print("="*50)
for vehicle in vehicles:
print(vehicle.get_info())
print(vehicle.honk())
print(vehicle.drive(100))
print()
# 트럭 전용 기능
print(truck.load_cargo(15))
print(truck.load_cargo(25))
실행 결과:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
==================================================
차량 정보
==================================================
2024년식 현대 소나타 (4도어)
빵빵!
100km 주행 완료 (총 주행거리: 100km)
2023년식 볼보 FH16 (적재량: 20톤)
빵빵!
100km 주행 완료 (총 주행거리: 100km)
2024년식 혼다 CB500X (500cc)
빠아앙!
100km 주행 완료 (총 주행거리: 100km)
✅ 15톤 적재 완료
❌ 적재 불가 (최대: 20톤)
💡 실전 팁 & 주의사항
Tip 1: super()를 사용하여 부모 메서드 호출하기
직접 부모 클래스 이름을 사용하는 것보다 super()를 사용하는 것이 더 유연하고 안전합니다.
[!NOTE]
super()와selfsuper().__init__()를 호출할 때는self를 넘기지 않아도 됩니다. 파이썬이 알아서 처리해주기 때문입니다! 반면,Parent.__init__(self)처럼 클래스 이름을 직접 쓸 때는 반드시self를 넣어줘야 합니다.
1
2
3
4
5
6
7
8
9
# ❌ 권장하지 않음
class Child(Parent):
def __init__(self):
Parent.__init__(self)
# ✅ 권장
class Child(Parent):
def __init__(self):
super().__init__()
Tip 2: “is-a” 관계일 때만 상속 사용하기
상속은 “~은 ~이다” 관계일 때 사용하세요. “~은 ~을 가진다” 관계라면 컴포지션을 사용하세요.
1
2
3
4
5
6
7
8
9
# ✅ 좋은 예: Student is a Person
class Student(Person):
pass
# ❌ 나쁜 예: Car is a Engine? (No!)
# Car has an Engine이므로 컴포지션 사용
class Car:
def __init__(self):
self.engine = Engine() # 컴포지션
Tip 3: 상속 깊이는 3단계 이하로 유지
너무 깊은 상속 계층은 코드를 복잡하게 만들고 유지보수를 어렵게 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ❌ 너무 깊은 상속
class A:
pass
class B(A):
pass
class C(B):
pass
class D(C): # 4단계, 너무 깊음
pass
# ✅ 적절한 상속 깊이
class Animal:
pass
class Dog(Animal): # 2단계, 적절함
pass
Tip 4: isinstance()로 타입 체크하기
런타임에 객체의 타입을 확인해야 할 때는 isinstance()를 사용하세요.
1
2
if isinstance(obj, Shape):
print(f"넓이: {obj.area()}")
📚 isinstance()와 issubclass()
isinstance(): 인스턴스 확인
1
2
3
4
5
6
7
8
9
10
11
class Animal:
pass
class Dog(Animal):
pass
dog = Dog()
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True (부모 클래스도 True!)
print(isinstance(dog, str)) # False
issubclass(): 상속 관계 확인
1
2
3
print(issubclass(Dog, Animal)) # True
print(issubclass(Animal, Dog)) # False
print(issubclass(Dog, Dog)) # True (자기 자신)
활용 예제
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
27
28
29
30
31
32
class Shape:
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14159 * self.radius ** 2
def print_area(shape):
"""도형의 넓이 출력"""
if isinstance(shape, Shape):
print(f"넓이: {shape.area():.2f}")
else:
print("❌ Shape 타입이 아닙니다.")
rect = Rectangle(5, 3)
circle = Circle(4)
print_area(rect) # 넓이: 15.00
print_area(circle) # 넓이: 50.27
print_area("문자열") # ❌ Shape 타입이 아닙니다.
🧪 연습 문제
문제 1: 은행 계좌 시스템
다음 요구사항을 만족하는 클래스들을 작성하세요:
요구사항:
BankAccount부모 클래스: 잔액 관리, 입출금 기능SavingsAccount자식 클래스: 이자 지급 기능 추가CheckingAccount자식 클래스: 수표 발행 기능 추가
1
2
3
4
5
6
7
# 사용 예시
savings = SavingsAccount("홍길동", 1000000, interest_rate=0.02)
print(savings.deposit(500000))
print(savings.apply_interest())
checking = CheckingAccount("김철수", 2000000, check_limit=500000)
print(checking.issue_check(300000))
✅ 정답
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
class BankAccount:
"""은행 계좌 클래스 (부모)"""
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
"""입금"""
if amount <= 0:
return "❌ 입금액은 0보다 커야 합니다."
self.balance += amount
return f"✅ {amount:,}원 입금 완료 (잔액: {self.balance:,}원)"
def withdraw(self, amount):
"""출금"""
if amount <= 0:
return "❌ 출금액은 0보다 커야 합니다."
if amount > self.balance:
return f"❌ 잔액 부족 (현재 잔액: {self.balance:,}원)"
self.balance -= amount
return f"✅ {amount:,}원 출금 완료 (잔액: {self.balance:,}원)"
def get_balance(self):
return f"{self.owner}님의 잔액: {self.balance:,}원"
class SavingsAccount(BankAccount):
"""저축 계좌 클래스 (자식)"""
def __init__(self, owner, balance=0, interest_rate=0.01):
super().__init__(owner, balance)
self.interest_rate = interest_rate
def apply_interest(self):
"""이자 지급"""
interest = int(self.balance * self.interest_rate)
self.balance += interest
return f"💰 이자 {interest:,}원 지급 (잔액: {self.balance:,}원)"
class CheckingAccount(BankAccount):
"""당좌 계좌 클래스 (자식)"""
def __init__(self, owner, balance=0, check_limit=1000000):
super().__init__(owner, balance)
self.check_limit = check_limit
def issue_check(self, amount):
"""수표 발행"""
if amount <= 0:
return "❌ 수표 금액은 0보다 커야 합니다."
if amount > self.check_limit:
return f"❌ 수표 한도 초과 (최대: {self.check_limit:,}원)"
if amount > self.balance:
return f"❌ 잔액 부족 (현재 잔액: {self.balance:,}원)"
self.balance -= amount
return f"📝 {amount:,}원 수표 발행 (잔액: {self.balance:,}원)"
# 테스트
savings = SavingsAccount("홍길동", 1000000, interest_rate=0.02)
print(savings.get_balance())
print(savings.deposit(500000))
print(savings.apply_interest())
print()
checking = CheckingAccount("김철수", 2000000, check_limit=500000)
print(checking.get_balance())
print(checking.issue_check(300000))
print(checking.issue_check(600000))
문제 2: 게임 캐릭터 시스템
Character 부모 클래스를 상속받는 Warrior, Mage, Archer 클래스를 작성하세요.
요구사항:
- 부모 클래스: 이름, HP, 공격력 관리
- 각 직업별 고유 스킬 구현
attack()메서드 오버라이딩
✅ 정답
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
class Character:
"""캐릭터 클래스 (부모)"""
def __init__(self, name, hp, attack_power):
self.name = name
self.hp = hp
self.max_hp = hp
self.attack_power = attack_power
def attack(self):
return f"{self.name}의 기본 공격! (데미지: {self.attack_power})"
def take_damage(self, damage):
self.hp = max(0, self.hp - damage)
if self.hp == 0:
return f"💀 {self.name}이(가) 쓰러졌습니다!"
return f"❤️ {self.name} HP: {self.hp}/{self.max_hp}"
class Warrior(Character):
"""전사 클래스"""
def __init__(self, name):
super().__init__(name, hp=150, attack_power=20)
def attack(self):
return f"⚔️ {self.name}의 강력한 검 공격! (데미지: {self.attack_power})"
def shield_bash(self):
damage = self.attack_power * 1.5
return f"🛡️ {self.name}의 방패 강타! (데미지: {damage:.0f})"
class Mage(Character):
"""마법사 클래스"""
def __init__(self, name):
super().__init__(name, hp=80, attack_power=30)
self.mana = 100
def attack(self):
return f"✨ {self.name}의 마법 공격! (데미지: {self.attack_power})"
def fireball(self):
if self.mana >= 30:
self.mana -= 30
damage = self.attack_power * 2
return f"🔥 {self.name}의 파이어볼! (데미지: {damage:.0f}, 마나: {self.mana})"
return "❌ 마나가 부족합니다!"
class Archer(Character):
"""궁수 클래스"""
def __init__(self, name):
super().__init__(name, hp=100, attack_power=25)
self.arrows = 20
def attack(self):
if self.arrows > 0:
self.arrows -= 1
return f"🏹 {self.name}의 화살 공격! (데미지: {self.attack_power}, 화살: {self.arrows})"
return "❌ 화살이 부족합니다!"
def multi_shot(self):
if self.arrows >= 3:
self.arrows -= 3
damage = self.attack_power * 2
return f"🎯 {self.name}의 다중 사격! (데미지: {damage:.0f}, 화살: {self.arrows})"
return "❌ 화살이 부족합니다!"
# 테스트
warrior = Warrior("전사")
mage = Mage("마법사")
archer = Archer("궁수")
characters = [warrior, mage, archer]
for char in characters:
print(char.attack())
print()
print(warrior.shield_bash())
print(mage.fireball())
print(archer.multi_shot())
📝 오늘 배운 내용 정리
| 개념 | 설명 | 예시 |
|---|---|---|
| 상속 | 기존 클래스 확장 | class Child(Parent): |
| 부모 클래스 | 상속을 제공하는 클래스 | Parent |
| 자식 클래스 | 상속을 받는 클래스 | Child |
| super() | 부모 메서드 호출 | super().__init__() |
| 오버라이딩 | 부모 메서드 재정의 | 같은 이름으로 메서드 작성 |
상속 사용 시기
✅ 상속을 사용할 때:
- “is-a” 관계 (Student is a Person)
- 코드 재사용이 필요할 때
- 공통 기능을 묶을 때
❌ 상속을 피해야 할 때:
- “has-a” 관계 (Car has a Engine) → 컴포지션 사용
- 단순히 코드 재사용만 목적일 때
- 너무 깊은 상속 계층 (3단계 이상)
🔗 관련 자료
- Python 공식 문서 - 상속
- Python 공식 문서 - super()
- Real Python - Inheritance and Composition
- Python 공식 문서 - isinstance()
📚 이전 학습
Day 34: 클래스 변수와 클래스 메서드 ⭐⭐⭐
어제는 클래스 변수와 클래스 메서드, @classmethod와 @staticmethod 데코레이터를 배웠습니다!
📚 다음 학습
Day 36: 메서드 오버라이딩과 다중 상속 ⭐⭐⭐⭐
내일은 메서드 오버라이딩 심화, 다중 상속(Multiple Inheritance), MRO(Method Resolution Order), 믹스인(Mixin) 패턴을 배웁니다!
“늦었다고 생각할 때가 가장 빠른 시기입니다!” 🚀
Day 35/100 Phase 4: 객체지향 프로그래밍 #100DaysOfPython

