[Python 100일 챌린지] Day 36 - 메서드 오버라이딩과 다중 상속
Day 35에서 부모 기능을 물려받았는데, 자식이 부모와 다르게 동작해야 한다면? 🤔 ──예를 들어
Animal.speak()는 “소리를 낸다”인데,Dog.speak()는 “멍멍!”이어야 합니다! 😊게임 캐릭터를 생각해보세요. 모든 캐릭터가 attack() 메서드를 가지지만, 전사는 “검으로 베기”, 마법사는 “파이어볼”, 궁수는 “화살 쏘기”로 구현됩니다. 같은 이름, 다른 동작!
스마트폰 알람도 마찬가지입니다. 기본 알람은 “띠리링~”이지만, 사용자가 원하는 소리로 재정의(Override) 할 수 있죠.
오늘 배울 메서드 오버라이딩으로 부모 기능을 내 입맛에 맞게 바꿉니다! 💡
🎯 오늘의 학습 목표
⭐⭐⭐⭐ (40-50분 완독)
📚 사전 지식
🎯 학습 목표 1: 메서드 오버라이딩의 개념 이해하기
메서드 오버라이딩이란?
메서드 오버라이딩(Method Overriding)은 자식 클래스에서 부모 클래스의 메서드를 같은 이름으로 재정의하는 것입니다. Day 35에서 기초를 배웠으니, 오늘은 두 가지 방식을 비교해봅시다!
완전 오버라이딩 vs 확장 오버라이딩
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
class Animal:
def speak(self):
return "동물이 소리를 냅니다."
class Dog(Animal):
# 완전 오버라이딩 - 부모 메서드 무시
def speak(self):
return "멍멍!"
class Cat(Animal):
# 확장 오버라이딩 - 부모 메서드 활용 + 추가
def speak(self):
parent_result = super().speak()
return f"{parent_result} 그리고 야옹~"
> [!TIP]
> **super()는 언제 쓰나요?**
> 부모의 원래 기능을 **유지하면서** 새로운 기능을 덧붙일 때 사용합니다. `super().메서드명()`으로 부모를 호출하는 것을 잊지 마세요!
dog = Dog()
cat = Cat()
print(dog.speak()) # 멍멍!
print(cat.speak()) # 동물이 소리를 냅니다. 그리고 야옹~
실전 예제: 파일 저장 시스템
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
class FileWriter:
"""파일 작성 클래스 (부모)"""
def __init__(self, filename):
self.filename = filename
def write(self, content):
"""기본 저장"""
return f"'{self.filename}'에 저장됨"
class EncryptedFileWriter(FileWriter):
"""암호화 파일 작성 클래스"""
def write(self, content):
# 부모 메서드 호출 전 암호화
encrypted = self._encrypt(content)
result = super().write(encrypted)
return f"🔒 암호화 후 {result}"
def _encrypt(self, content):
# 간단한 암호화 (실제로는 복잡한 알고리즘 사용)
return f"encrypted_{content}"
class CompressedFileWriter(FileWriter):
"""압축 파일 작성 클래스"""
def write(self, content):
# 부모 메서드 호출 전 압축
compressed = self._compress(content)
result = super().write(compressed)
return f"📦 압축 후 {result}"
def _compress(self, content):
return f"compressed_{content}"
# 사용
normal = FileWriter("normal.txt")
encrypted = EncryptedFileWriter("secure.txt")
compressed = CompressedFileWriter("archive.txt")
print(normal.write("Hello World"))
print(encrypted.write("Secret Data"))
print(compressed.write("Large File"))
실행 결과:
1
2
3
'normal.txt'에 저장됨
🔒 암호화 후 'secure.txt'에 저장됨
📦 압축 후 'archive.txt'에 저장됨
🎯 학습 목표 2: 다중 상속 이해하기
다중 상속 (Multiple Inheritance)이란?
Python은 여러 부모 클래스를 동시에 상속받을 수 있습니다!
기본 문법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Parent1:
def method1(self):
return "Parent1의 메서드"
class Parent2:
def method2(self):
return "Parent2의 메서드"
class Child(Parent1, Parent2):
"""Parent1과 Parent2를 모두 상속"""
pass
child = Child()
print(child.method1()) # Parent1의 메서드
print(child.method2()) # Parent2의 메서드
다이아몬드 문제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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):
"""B와 C 모두 상속 - 어떤 method가 호출될까?"""
pass
d = D()
print(d.method()) # B (왼쪽 부모 우선)
# MRO 확인
print(D.__mro__)
# (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
🎯 학습 목표 3: MRO(메서드 검색 순서) 이해하기
MRO (Method Resolution Order)란?
Python이 메서드를 찾는 순서입니다.
MRO 확인 방법
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
# 방법 1: __mro__ 속성
print(D.__mro__)
# 방법 2: mro() 메서드
print(D.mro())
# 방법 3: 간단하게 출력
for i, cls in enumerate(D.mro()):
print(f"{i}. {cls.__name__}")
실행 결과:
1
2
3
4
5
0. D
1. B
2. C
3. A
4. object
실전 예제: 로깅 시스템
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
class Logger:
"""로깅 기능"""
def log(self, message):
print(f"[LOG] {message}")
class TimestampMixin:
"""타임스탬프 기능"""
def get_timestamp(self):
from datetime import datetime
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
class ColorMixin:
"""색상 기능"""
COLORS = {
'RED': '\033[91m',
'GREEN': '\033[92m',
'YELLOW': '\033[93m',
'BLUE': '\033[94m',
'END': '\033[0m'
}
def colorize(self, text, color='GREEN'):
return f"{self.COLORS.get(color, '')}{text}{self.COLORS['END']}"
class AdvancedLogger(Logger, TimestampMixin, ColorMixin):
"""고급 로거 - 다중 상속"""
def log(self, message, level='INFO'):
timestamp = self.get_timestamp()
colored_level = self.colorize(level, 'YELLOW')
colored_msg = self.colorize(message, 'GREEN')
print(f"[{timestamp}] [{colored_level}] {colored_msg}")
# 사용
logger = AdvancedLogger()
logger.log("프로그램 시작", "INFO")
logger.log("작업 완료", "SUCCESS")
logger.log("오류 발생", "ERROR")
print("\nMRO:")
for i, cls in enumerate(AdvancedLogger.mro()):
print(f"{i}. {cls.__name__}")
🎯 학습 목표 4: 믹스인 패턴 활용하기
믹스인 (Mixin) 패턴이란?
믹스인은 기능을 추가하기 위한 작은 클래스입니다. 마치 게임 캐릭터에 ‘아이템’을 장착하듯, 필요한 기능만 쏙쏙 골라 클래스에 붙여줄 수 있습니다.
믹스인 설계 원칙
- 단일 기능에 집중
- 독립적으로 동작
- 이름에
Mixin접미사 사용
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
class JSONMixin:
"""JSON 직렬화 기능"""
def to_json(self):
import json
data = {
key: value for key, value in self.__dict__.items()
if not key.startswith('_')
}
return json.dumps(data, ensure_ascii=False, indent=2)
class ComparableMixin:
"""비교 기능"""
def __eq__(self, other):
if not isinstance(other, self.__class__):
return False
return self.__dict__ == other.__dict__
def __ne__(self, other):
return not self.__eq__(other)
class PrintableMixin:
"""출력 기능"""
def print_info(self):
print("="*50)
for key, value in self.__dict__.items():
if not key.startswith('_'):
print(f"{key}: {value}")
print("="*50)
class Person(JSONMixin, ComparableMixin, PrintableMixin):
"""믹스인을 활용한 Person 클래스"""
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
# 사용
person1 = Person("홍길동", 30, "hong@example.com")
person2 = Person("홍길동", 30, "hong@example.com")
person3 = Person("김철수", 25, "kim@example.com")
# JSONMixin 기능
print("JSON 변환:")
print(person1.to_json())
# ComparableMixin 기능
print(f"\nperson1 == person2: {person1 == person2}") # True
print(f"person1 == person3: {person1 == person3}") # False
# PrintableMixin 기능
print("\n정보 출력:")
person1.print_info()
실행 결과:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
JSON 변환:
{
"name": "홍길동",
"age": 30,
"email": "hong@example.com"
}
person1 == person2: True
person1 == person3: False
정보 출력:
==================================================
name: 홍길동
age: 30
email: hong@example.com
==================================================
🚀 실전 예제: 완전한 데이터 모델
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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import json
from datetime import datetime
# 믹스인 클래스들
class TimestampMixin:
"""생성/수정 시간 추적"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.created_at = datetime.now()
self.updated_at = datetime.now()
def touch(self):
"""수정 시간 업데이트"""
self.updated_at = datetime.now()
class SerializableMixin:
"""직렬화 기능"""
def to_dict(self):
"""딕셔너리로 변환"""
result = {}
for key, value in self.__dict__.items():
if isinstance(value, datetime):
result[key] = value.isoformat()
else:
result[key] = value
return result
def to_json(self):
"""JSON 문자열로 변환"""
return json.dumps(self.to_dict(), ensure_ascii=False, indent=2)
class ValidatableMixin:
"""유효성 검사 기능"""
def validate(self):
"""자식 클래스에서 구현"""
raise NotImplementedError("validate() 메서드를 구현하세요")
def is_valid(self):
"""유효성 검사 실행"""
try:
self.validate()
return True
except ValueError:
return False
# 실제 모델 클래스
class Product(TimestampMixin, SerializableMixin, ValidatableMixin):
"""상품 모델"""
def __init__(self, name, price, stock):
super().__init__()
self.name = name
self.price = price
self.stock = stock
def validate(self):
"""유효성 검사"""
if not self.name or len(self.name) < 2:
raise ValueError("상품명은 2자 이상이어야 합니다")
if self.price < 0:
raise ValueError("가격은 0 이상이어야 합니다")
if self.stock < 0:
raise ValueError("재고는 0 이상이어야 합니다")
def update_price(self, new_price):
"""가격 변경"""
if new_price < 0:
raise ValueError("가격은 0 이상이어야 합니다")
self.price = new_price
self.touch() # TimestampMixin에서 제공
def sell(self, quantity):
"""판매"""
if quantity > self.stock:
raise ValueError("재고가 부족합니다")
self.stock -= quantity
self.touch()
def __str__(self):
return f"{self.name} ({self.price:,}원, 재고: {self.stock}개)"
# 사용
product = Product("노트북", 1500000, 10)
print("초기 상품:")
print(product)
print(f"유효성: {product.is_valid()}")
# 가격 변경
product.update_price(1400000)
print(f"\n가격 변경 후: {product}")
# 판매
product.sell(3)
print(f"판매 후: {product}")
# JSON 변환
print("\nJSON:")
print(product.to_json())
# 딕셔너리 변환
print("\n딕셔너리:")
print(product.to_dict())
# 잘못된 데이터
print("\n유효성 검사:")
invalid_product = Product("", -1000, -5)
print(f"유효성: {invalid_product.is_valid()}")
try:
invalid_product.validate()
except ValueError as e:
print(f"에러: {e}")
실행 결과:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
초기 상품:
노트북 (1,500,000원, 재고: 10개)
유효성: True
가격 변경 후: 노트북 (1,400,000원, 재고: 10개)
판매 후: 노트북 (1,400,000원, 재고: 7개)
JSON:
{
"created_at": "2025-04-05T10:30:15.123456",
"updated_at": "2025-04-05T10:30:16.789012",
"name": "노트북",
"price": 1400000,
"stock": 7
}
딕셔너리:
{'created_at': '2025-04-05T10:30:15.123456', 'updated_at': '2025-04-05T10:30:16.789012', 'name': '노트북', 'price': 1400000, 'stock': 7}
유효성 검사:
유효성: False
에러: 상품명은 2자 이상이어야 합니다
💡 실전 팁 & 주의사항
Tip 1: 완전 오버라이딩보다 확장 오버라이딩 활용하기
부모의 기능을 완전히 무시하기보다는 super()를 활용하여 확장하는 것이 더 유연합니다.
1
2
3
4
5
# ✅ 권장: 확장 오버라이딩
class Child(Parent):
def method(self):
result = super().method() # 부모 기능 활용
return result + " + 추가 기능"
Tip 2: 다중 상속은 신중하게 사용하기
다중 상속은 강력하지만 복잡도를 높일 수 있습니다. 믹스인 패턴을 사용하여 깔끔하게 설계하세요.
1
2
3
4
5
6
7
# ✅ 좋은 예: 믹스인 패턴
class JSONMixin:
def to_json(self):
return json.dumps(self.__dict__)
class User(JSONMixin): # 단일 기능만 추가
pass
Tip 3: MRO를 이해하고 활용하기
복잡한 상속 구조에서는 Class.mro()로 메서드 검색 순서를 확인하세요.
1
2
# MRO 확인
print(MyClass.mro())
Tip 4: 다이아몬드 문제 주의하기
다이아몬드 상속 구조에서는 super()를 사용하여 모든 부모가 한 번씩만 호출되도록 하세요.
1
2
3
4
# ✅ super() 사용으로 안전하게 처리
class D(B, C):
def __init__(self):
super().__init__() # MRO 순서대로 호출
🧪 연습 문제
문제 1: 게임 아이템 시스템
믹스인을 활용하여 게임 아이템 시스템을 구현하세요:
요구사항:
EquippableMixin: 장착/해제 기능UpgradeableMixin: 강화 기능Item클래스: 믹스인 활용
1
2
3
4
5
# 사용 예시
sword = Weapon("전설의 검", 100)
sword.equip()
sword.upgrade()
print(sword.get_power())
✅ 정답
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
class EquippableMixin:
"""장착 가능 믹스인"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.equipped = False
def equip(self):
if self.equipped:
return "❌ 이미 장착 중입니다"
self.equipped = True
return f"✅ {self.name}을(를) 장착했습니다"
def unequip(self):
if not self.equipped:
return "❌ 장착되지 않았습니다"
self.equipped = False
return f"✅ {self.name}을(를) 해제했습니다"
class UpgradeableMixin:
"""강화 가능 믹스인"""
MAX_LEVEL = 10
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.level = 0
def upgrade(self):
if self.level >= self.MAX_LEVEL:
return f"❌ 최대 레벨({self.MAX_LEVEL})입니다"
self.level += 1
return f"⬆️ {self.name}이(가) +{self.level}로 강화되었습니다!"
class Item:
"""아이템 기본 클래스"""
def __init__(self, name):
self.name = name
class Weapon(Item, EquippableMixin, UpgradeableMixin):
"""무기 클래스"""
def __init__(self, name, base_power):
super().__init__(name)
self.base_power = base_power
def get_power(self):
bonus = self.base_power * (self.level * 0.1)
return int(self.base_power + bonus)
def __str__(self):
power = self.get_power()
status = "⚔️" if self.equipped else "📦"
return f"{status} {self.name} +{self.level} (공격력: {power})"
# 테스트
sword = Weapon("전설의 검", 100)
print(sword)
print(sword.equip())
print(sword.upgrade())
print(sword.upgrade())
print(sword.upgrade())
print(sword)
print(f"공격력: {sword.get_power()}")
문제 2: 블로그 시스템
다중 상속과 믹스인으로 블로그 시스템을 구현하세요:
요구사항:
Post기본 클래스PublishableMixin: 발행/비공개 기능CommentableMixin: 댓글 기능BlogPost: 모든 기능 통합
✅ 정답
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
from datetime import datetime
class PublishableMixin:
"""발행 관리 믹스인"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.is_published = False
self.published_at = None
def publish(self):
if self.is_published:
return "❌ 이미 발행되었습니다"
self.is_published = True
self.published_at = datetime.now()
return f"✅ '{self.title}'이(가) 발행되었습니다"
def unpublish(self):
if not self.is_published:
return "❌ 발행되지 않았습니다"
self.is_published = False
return f"✅ '{self.title}'이(가) 비공개 처리되었습니다"
class CommentableMixin:
"""댓글 기능 믹스인"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.comments = []
def add_comment(self, author, content):
comment = {
'author': author,
'content': content,
'created_at': datetime.now()
}
self.comments.append(comment)
return f"💬 {author}님이 댓글을 작성했습니다"
def get_comment_count(self):
return len(self.comments)
def print_comments(self):
if not self.comments:
print("댓글이 없습니다.")
return
print(f"\n댓글 ({self.get_comment_count()}개):")
for i, comment in enumerate(self.comments, 1):
time = comment['created_at'].strftime("%Y-%m-%d %H:%M")
print(f"{i}. {comment['author']} ({time})")
print(f" {comment['content']}")
class Post:
"""포스트 기본 클래스"""
def __init__(self, title, content, author):
self.title = title
self.content = content
self.author = author
self.created_at = datetime.now()
class BlogPost(Post, PublishableMixin, CommentableMixin):
"""블로그 포스트 - 모든 기능 통합"""
def __str__(self):
status = "🟢 발행됨" if self.is_published else "🔴 비공개"
return f"[{status}] {self.title} by {self.author} (댓글: {self.get_comment_count()})"
# 테스트
post = BlogPost(
title="Python 객체지향 프로그래밍",
content="OOP는 재미있습니다!",
author="홍길동"
)
print(post)
print(post.publish())
print(post.add_comment("김철수", "좋은 글 감사합니다!"))
print(post.add_comment("이영희", "도움이 많이 되었어요"))
print(post)
post.print_comments()
📝 오늘 배운 내용 정리
| 개념 | 설명 | 예시 |
|---|---|---|
| 오버라이딩 | 부모 메서드 재정의 | def method(self): |
| 다중 상속 | 여러 부모 상속 | class C(A, B): |
| MRO | 메서드 검색 순서 | Class.mro() |
| 믹스인 | 기능 추가 클래스 | class JSONMixin: |
다중 상속 사용 시 주의사항
✅ 권장:
- 믹스인 패턴 사용
- 명확한 역할 분리
- MRO 이해하고 활용
❌ 주의:
- 복잡한 상속 계층
- 다이아몬드 문제 무시
- 책임이 불명확한 클래스
🔗 관련 자료
📚 이전 학습
Day 35: 상속의 기초 ⭐⭐⭐
어제는 상속을 통한 코드 재사용, super()로 부모 클래스 접근하기, isinstance()와 issubclass() 활용법을 배웠습니다!
📚 다음 학습
Day 37: 캡슐화와 정보 은닉 심화 ⭐⭐⭐⭐
내일은 캡슐화 원칙 심화, 접근 제어자 패턴, Property 데코레이터 고급 활용, 불변 객체(Immutable Object) 설계를 배웁니다!
“늦었다고 생각할 때가 가장 빠른 시기입니다!” 🚀
Day 36/100 Phase 4: 객체지향 프로그래밍 #100DaysOfPython



