[이제와서 시작하는 Python 마스터하기 #8] 클래스와 객체지향 프로그래밍
[이제와서 시작하는 Python 마스터하기 #8] 클래스와 객체지향 프로그래밍
🏢 5분만에 만드는 회사 관리 시스템
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
from datetime import datetime, date
from typing import List, Dict, Optional
import json
class Employee:
"""직원 클래스 - OOP 기본 개념 활용"""
# 클래스 변수
company_name = "파이썬 테크"
total_employees = 0
def __init__(self, name: str, department: str, position: str, salary: int):
self.id = self._generate_id()
self.name = name
self.department = department
self.position = position
self._salary = salary # 프라이빗 속성
self.hire_date = date.today()
self.projects = []
Employee.total_employees += 1
def _generate_id(self) -> str:
"""직원 ID 생성"""
return f"EMP{Employee.total_employees + 1:04d}"
@property
def salary(self) -> int:
"""급여 getter"""
return self._salary
@salary.setter
def salary(self, value: int):
"""급여 setter (검증 포함)"""
if value < 0:
raise ValueError("급여는 음수일 수 없습니다")
self._salary = value
def get_annual_salary(self) -> int:
"""연봉 계산"""
return self._salary * 12
def add_project(self, project_name: str):
"""프로젝트 추가"""
self.projects.append({
"name": project_name,
"start_date": date.today().isoformat()
})
def __str__(self):
return f"{self.name} ({self.position}, {self.department})"
def __repr__(self):
return f"Employee('{self.name}', '{self.department}', '{self.position}', {self._salary})"
class Manager(Employee):
"""매니저 클래스 - 상속 활용"""
def __init__(self, name: str, department: str, salary: int):
super().__init__(name, department, "Manager", salary)
self.team_members: List[Employee] = []
def add_team_member(self, employee: Employee):
"""팀원 추가"""
if employee not in self.team_members:
self.team_members.append(employee)
print(f"✅ {employee.name}님이 {self.name}의 팀에 추가되었습니다")
def get_team_performance(self) -> Dict:
"""팀 성과 분석"""
total_projects = sum(len(emp.projects) for emp in self.team_members)
avg_salary = sum(emp.salary for emp in self.team_members) / len(self.team_members) if self.team_members else 0
return {
"team_size": len(self.team_members),
"total_projects": total_projects,
"average_salary": avg_salary,
"manager": self.name
}
class Company:
"""회사 클래스 - 종합 관리"""
def __init__(self, name: str):
self.name = name
self.employees: List[Employee] = []
self.departments: Dict[str, List[Employee]] = {}
def hire(self, employee: Employee):
"""직원 채용"""
self.employees.append(employee)
# 부서별 분류
if employee.department not in self.departments:
self.departments[employee.department] = []
self.departments[employee.department].append(employee)
print(f"🎉 {employee.name}님이 {employee.department}팀에 입사했습니다!")
def get_payroll(self) -> int:
"""전체 급여 총액"""
return sum(emp.salary for emp in self.employees)
def get_department_stats(self) -> Dict:
"""부서별 통계"""
stats = {}
for dept, employees in self.departments.items():
stats[dept] = {
"count": len(employees),
"total_salary": sum(emp.salary for emp in employees),
"positions": list(set(emp.position for emp in employees))
}
return stats
def promote(self, employee: Employee, new_position: str, salary_increase: int):
"""승진 처리"""
old_position = employee.position
employee.position = new_position
employee.salary += salary_increase
print(f"📈 {employee.name}님이 {old_position}에서 {new_position}(으)로 승진했습니다!")
print(f" 새 급여: {employee.salary:,}원/월")
# 사용 예제
def company_demo():
"""회사 관리 시스템 데모"""
# 회사 생성
company = Company("파이썬 테크")
# 직원 채용
emp1 = Employee("김철수", "개발팀", "Junior Developer", 3500000)
emp2 = Employee("이영희", "개발팀", "Senior Developer", 5500000)
emp3 = Employee("박민수", "마케팅팀", "Marketing Specialist", 4000000)
company.hire(emp1)
company.hire(emp2)
company.hire(emp3)
# 매니저 채용
manager = Manager("정대리", "개발팀", 7000000)
company.hire(manager)
# 팀 구성
manager.add_team_member(emp1)
manager.add_team_member(emp2)
# 프로젝트 할당
emp1.add_project("웹사이트 리뉴얼")
emp2.add_project("API 개발")
emp2.add_project("데이터베이스 최적화")
# 승진
company.promote(emp1, "Mid-level Developer", 500000)
# 통계 출력
print(f"\n📊 회사 통계")
print(f"총 직원 수: {Employee.total_employees}명")
print(f"월 급여 총액: {company.get_payroll():,}원")
print(f"\n부서별 현황:")
for dept, stats in company.get_department_stats().items():
print(f" {dept}: {stats['count']}명, 급여총액: {stats['total_salary']:,}원")
# 팀 성과
print(f"\n🏆 {manager.name} 팀 성과:")
performance = manager.get_team_performance()
for key, value in performance.items():
print(f" {key}: {value}")
# 실행
# company_demo()
🎭 객체지향 프로그래밍(OOP)이란?
객체지향 프로그래밍은 프로그램을 객체들의 모임으로 보고, 객체들 간의 상호작용으로 프로그램을 구성하는 프로그래밍 패러다임입니다.
graph TD
A[객체지향 프로그래밍] --> B[캡슐화<br/>Encapsulation]
A --> C[상속<br/>Inheritance]
A --> D[다형성<br/>Polymorphism]
A --> E[추상화<br/>Abstraction]
B --> B1[데이터와 메서드를<br/>하나로 묶음]
C --> C1[기존 클래스의<br/>기능 재사용]
D --> D1[같은 인터페이스<br/>다른 동작]
E --> E1[복잡함을 숨기고<br/>핵심만 노출]
📦 클래스와 객체
🎮 실전 예제: 게임 캐릭터 시스템
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
import random
from enum import Enum
from typing import Optional
class CharacterClass(Enum):
"""캐릭터 클래스 열거형"""
WARRIOR = "전사"
MAGE = "마법사"
ARCHER = "궁수"
HEALER = "힐러"
class GameCharacter:
"""게임 캐릭터 클래스"""
# 클래스 변수
max_level = 100
base_hp = 100
base_mp = 50
def __init__(self, name: str, character_class: CharacterClass):
# 인스턴스 변수
self.name = name
self.character_class = character_class
self.level = 1
self.exp = 0
self.exp_to_next = 100
# 클래스별 스탯 설정
self._initialize_stats()
self.current_hp = self.max_hp
self.current_mp = self.max_mp
# 인벤토리와 스킬
self.inventory = []
self.skills = self._get_initial_skills()
self.gold = 100
def _initialize_stats(self):
"""클래스별 초기 스탯 설정"""
stats = {
CharacterClass.WARRIOR: {"hp_mult": 1.5, "mp_mult": 0.5, "atk": 15, "def": 10},
CharacterClass.MAGE: {"hp_mult": 0.7, "mp_mult": 2.0, "atk": 20, "def": 5},
CharacterClass.ARCHER: {"hp_mult": 1.0, "mp_mult": 1.0, "atk": 12, "def": 7},
CharacterClass.HEALER: {"hp_mult": 0.8, "mp_mult": 1.5, "atk": 8, "def": 8}
}
class_stats = stats[self.character_class]
self.max_hp = int(self.base_hp * class_stats["hp_mult"])
self.max_mp = int(self.base_mp * class_stats["mp_mult"])
self.attack = class_stats["atk"]
self.defense = class_stats["def"]
def _get_initial_skills(self) -> List[str]:
"""클래스별 초기 스킬"""
skills = {
CharacterClass.WARRIOR: ["강타", "방어 태세"],
CharacterClass.MAGE: ["파이어볼", "아이스 실드"],
CharacterClass.ARCHER: ["더블 샷", "회피"],
CharacterClass.HEALER: ["치유", "축복"]
}
return skills[self.character_class]
def attack_monster(self, monster_name: str) -> Dict:
"""몬스터 공격"""
damage = random.randint(self.attack - 2, self.attack + 5) * self.level
crit = random.random() < 0.2 # 20% 크리티컬 확률
if crit:
damage *= 2
print(f"💥 크리티컬! {self.name}이(가) {monster_name}에게 {damage} 피해를 입혔습니다!")
else:
print(f"⚔️ {self.name}이(가) {monster_name}에게 {damage} 피해를 입혔습니다.")
# 경험치 획득
exp_gained = random.randint(10, 30)
self.gain_exp(exp_gained)
return {"damage": damage, "critical": crit, "exp": exp_gained}
def gain_exp(self, amount: int):
"""경험치 획득"""
self.exp += amount
print(f"✨ {amount} 경험치 획득!")
# 레벨업 체크
while self.exp >= self.exp_to_next and self.level < self.max_level:
self.level_up()
def level_up(self):
"""레벨업"""
self.level += 1
self.exp -= self.exp_to_next
self.exp_to_next = int(self.exp_to_next * 1.2)
# 스탯 증가
hp_increase = random.randint(10, 20)
mp_increase = random.randint(5, 10)
self.max_hp += hp_increase
self.max_mp += mp_increase
self.attack += random.randint(2, 4)
self.defense += random.randint(1, 3)
# 체력/마나 회복
self.current_hp = self.max_hp
self.current_mp = self.max_mp
print(f"🎉 레벨업! {self.name}님이 레벨 {self.level}이(가) 되었습니다!")
print(f" HP +{hp_increase}, MP +{mp_increase}")
def use_skill(self, skill_name: str) -> bool:
"""스킬 사용"""
if skill_name not in self.skills:
print(f"❌ {skill_name} 스킬이 없습니다.")
return False
mp_cost = 10 * self.level
if self.current_mp < mp_cost:
print(f"❌ 마나가 부족합니다. (필요: {mp_cost}, 현재: {self.current_mp})")
return False
self.current_mp -= mp_cost
print(f"🎯 {self.name}이(가) {skill_name}을(를) 사용했습니다! (MP -{mp_cost})")
return True
def add_item(self, item_name: str, quantity: int = 1):
"""아이템 추가"""
self.inventory.append({"name": item_name, "quantity": quantity})
print(f"📦 {item_name} x{quantity}을(를) 획득했습니다!")
def show_stats(self):
"""캐릭터 정보 표시"""
print(f"\n{'='*40}")
print(f"🎮 {self.name} ({self.character_class.value})")
print(f"{'='*40}")
print(f"레벨: {self.level} (경험치: {self.exp}/{self.exp_to_next})")
print(f"HP: {self.current_hp}/{self.max_hp}")
print(f"MP: {self.current_mp}/{self.max_mp}")
print(f"공격력: {self.attack} | 방어력: {self.defense}")
print(f"골드: {self.gold}G")
print(f"스킬: {', '.join(self.skills)}")
print(f"인벤토리: {len(self.inventory)}개 아이템")
print(f"{'='*40}")
# 사용 예제
def game_demo():
"""게임 캐릭터 시스템 데모"""
# 캐릭터 생성
warrior = GameCharacter("김전사", CharacterClass.WARRIOR)
mage = GameCharacter("이마법사", CharacterClass.MAGE)
# 전투 시뮬레이션
print("⚔️ 전투 시작!\n")
for round in range(1, 4):
print(f"\n=== 라운드 {round} ===")
# 전사 공격
warrior.attack_monster("고블린")
warrior.use_skill("강타")
# 마법사 공격
mage.attack_monster("슬라임")
mage.use_skill("파이어볼")
# 아이템 획득
warrior.add_item("체력 포션", 3)
mage.add_item("마나 포션", 2)
# 캐릭터 정보 출력
warrior.show_stats()
mage.show_stats()
# 실행
# game_demo()
클래스 정의와 객체 생성
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
# 클래스 정의
class Person:
"""사람을 나타내는 클래스"""
# 클래스 변수 (모든 인스턴스가 공유)
species = "Homo sapiens"
# 생성자 (초기화 메서드)
def __init__(self, name, age):
# 인스턴스 변수
self.name = name
self.age = age
# 인스턴스 메서드
def introduce(self):
return f"안녕하세요, 저는 {self.name}이고 {self.age}살입니다."
def have_birthday(self):
"""생일 - 나이 증가"""
self.age += 1
print(f"{self.name}님의 생일을 축하합니다! 이제 {self.age}살이 되었습니다.")
# 객체(인스턴스) 생성
person1 = Person("김철수", 25)
person2 = Person("이영희", 30)
# 메서드 호출
print(person1.introduce())
person1.have_birthday()
# 속성 접근
print(f"{person1.name}의 나이: {person1.age}")
print(f"종: {Person.species}") # 클래스 변수는 클래스명으로도 접근 가능
속성과 메서드
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
class BankAccount:
"""은행 계좌 클래스"""
# 클래스 변수
bank_name = "파이썬 은행"
account_count = 0
def __init__(self, owner, initial_balance=0):
# 인스턴스 변수
self.owner = owner
self.balance = initial_balance
self._account_number = self._generate_account_number()
self.__pin = None # 프라이빗 변수 (네임 맹글링)
# 클래스 변수 업데이트
BankAccount.account_count += 1
def _generate_account_number(self):
"""계좌번호 생성 (내부용 메서드)"""
import random
return f"PY{random.randint(10000000, 99999999)}"
def deposit(self, amount):
"""입금"""
if amount > 0:
self.balance += amount
print(f"{amount}원 입금 완료. 잔액: {self.balance}원")
else:
print("입금액은 0보다 커야 합니다.")
def withdraw(self, amount):
"""출금"""
if amount <= 0:
print("출금액은 0보다 커야 합니다.")
elif amount > self.balance:
print("잔액이 부족합니다.")
else:
self.balance -= amount
print(f"{amount}원 출금 완료. 잔액: {self.balance}원")
def get_balance(self):
"""잔액 조회"""
return self.balance
def __str__(self):
"""객체의 문자열 표현"""
return f"{self.owner}님의 계좌 (잔액: {self.balance}원)"
def __repr__(self):
"""개발자를 위한 문자열 표현"""
return f"BankAccount('{self.owner}', {self.balance})"
# 사용 예제
account = BankAccount("홍길동", 10000)
account.deposit(5000)
account.withdraw(3000)
print(account) # __str__ 호출
print(repr(account)) # __repr__ 호출
[!TIP] __str__ vs __repr__ 차이점
__str__: 사용자를 위한 예쁜 출력 (print() 할 때 나옴)__repr__: 개발자를 위한 엄격한 출력 (디버깅할 때 씀)헷갈리면 일단
__str__만이라도 잘 만들어두세요! 디버깅이 훨씬 편해집니다.
🎨 Dataclass와 Pydantic으로 더 스마트한 클래스 만들기
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
from dataclasses import dataclass, field
from typing import List, Optional
from datetime import datetime
# dataclass 기본 사용법
@dataclass
class Product:
"""제품 클래스 - dataclass 사용"""
name: str
price: float
quantity: int = 0
tags: List[str] = field(default_factory=list)
def total_value(self) -> float:
"""재고 총 가치"""
return self.price * self.quantity
def add_tag(self, tag: str):
"""태그 추가"""
if tag not in self.tags:
self.tags.append(tag)
# 사용 예제
product = Product("노트북", 1500000, 10)
product.add_tag("전자제품")
product.add_tag("컴퓨터")
print(product) # 자동으로 __repr__ 생성됨
print(f"총 가치: {product.total_value():,}원")
# dataclass 고급 기능
@dataclass(frozen=True) # 불변 객체
class Point3D:
"""3차원 좌표 - 불변 dataclass"""
x: float
y: float
z: float
def distance_from_origin(self) -> float:
"""원점으로부터의 거리"""
return (self.x**2 + self.y**2 + self.z**2) ** 0.5
@dataclass(order=True) # 비교 연산자 자동 생성
class Student:
"""학생 클래스 - 정렬 가능한 dataclass"""
sort_index: float = field(init=False, repr=False)
name: str
grade: float
student_id: str = field(compare=False)
def __post_init__(self):
"""초기화 후 처리"""
self.sort_index = self.grade
# 학생 정렬 예제
students = [
Student("김철수", 4.2, "2024001"),
Student("이영희", 4.5, "2024002"),
Student("박민수", 3.8, "2024003"),
]
sorted_students = sorted(students, reverse=True)
for student in sorted_students:
print(f"{student.name}: {student.grade}")
Pydantic으로 데이터 검증하기
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
# pip install pydantic
from pydantic import BaseModel, Field, validator
from typing import Optional, List
from datetime import datetime
from enum import Enum
class UserRole(str, Enum):
"""사용자 권한 열거형"""
ADMIN = "admin"
USER = "user"
GUEST = "guest"
class User(BaseModel):
"""사용자 모델 - Pydantic으로 자동 검증"""
username: str = Field(..., min_length=3, max_length=20, regex="^[a-zA-Z0-9_]+$")
email: str = Field(..., regex=r"^[\w\.-]+@[\w\.-]+\.\w+$")
age: int = Field(..., ge=0, le=150) # 0 <= age <= 150
role: UserRole = UserRole.USER
is_active: bool = True
created_at: datetime = Field(default_factory=datetime.now)
bio: Optional[str] = Field(None, max_length=500)
@validator('email')
def validate_email(cls, v):
"""이메일 도메인 검증"""
if not v.endswith('.com') and not v.endswith('.kr'):
raise ValueError('이메일은 .com 또는 .kr 도메인이어야 합니다')
return v.lower()
@validator('username')
def username_alphanumeric(cls, v):
"""사용자명 검증"""
if v[0].isdigit():
raise ValueError('사용자명은 숫자로 시작할 수 없습니다')
return v
class Config:
"""Pydantic 설정"""
json_encoders = {
datetime: lambda v: v.isoformat()
}
schema_extra = {
"example": {
"username": "john_doe",
"email": "john@example.com",
"age": 25,
"role": "user",
"bio": "Python 개발자입니다"
}
}
# 사용 예제
try:
# 유효한 사용자
user = User(
username="python_lover",
email="python@example.com",
age=28
)
print(user.json(indent=2))
# 유효하지 않은 사용자 (에러 발생)
# invalid_user = User(
# username="123invalid", # 숫자로 시작
# email="not_an_email", # 잘못된 이메일 형식
# age=200 # 범위 초과
# )
except ValueError as e:
print(f"검증 실패: {e}")
# API 응답 모델 예제
class BlogPost(BaseModel):
"""블로그 포스트 모델"""
title: str = Field(..., min_length=1, max_length=200)
content: str
author: User
tags: List[str] = []
published: bool = False
views: int = Field(default=0, ge=0)
@validator('tags')
def validate_tags(cls, v):
"""태그 검증 및 정규화"""
return [tag.lower().strip() for tag in v if tag.strip()]
def publish(self):
"""포스트 발행"""
self.published = True
return self
# 사용 예제
post = BlogPost(
title="Python Dataclass vs Pydantic",
content="두 라이브러리의 차이점을 알아봅시다...",
author=user,
tags=["Python", "OOP", "Validation"]
)
print(f"포스트 제목: {post.title}")
print(f"작성자: {post.author.username}")
print(f"태그: {post.tags}")
프로퍼티(Property)
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
"""온도 클래스 - 섭씨와 화씨 변환"""
def __init__(self, celsius=0):
self._celsius = celsius
@property
def celsius(self):
"""섭씨 온도 getter"""
return self._celsius
@celsius.setter
def celsius(self, value):
"""섭씨 온도 setter"""
if value < -273.15:
raise ValueError("절대영도(-273.15°C)보다 낮을 수 없습니다.")
self._celsius = value
@property
def fahrenheit(self):
"""화씨 온도 getter"""
return self._celsius * 9/5 + 32
@fahrenheit.setter
def fahrenheit(self, value):
"""화씨 온도 setter"""
self.celsius = (value - 32) * 5/9
사용 예제
temp = Temperature() temp.celsius = 25 print(f”섭씨: {temp.celsius}°C”) print(f”화씨: {temp.fahrenheit}°F”)
temp.fahrenheit = 86 print(f”섭씨: {temp.celsius}°C”)
프로퍼티를 사용한 계산된 속성
class Circle: “"”원 클래스”””
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value <= 0:
raise ValueError("반지름은 양수여야 합니다.")
self._radius = value
@property
def area(self):
"""원의 넓이 (읽기 전용)"""
import math
return math.pi * self._radius ** 2
@property
def circumference(self):
"""원의 둘레 (읽기 전용)"""
import math
return 2 * math.pi * self._radius
circle = Circle(5) print(f”반지름: {circle.radius}”) print(f”넓이: {circle.area:.2f}”) print(f”둘레: {circle.circumference:.2f}”)
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
## 🧬 상속(Inheritance)
### 기본 상속
```python
# 부모 클래스 (기반 클래스, 슈퍼 클래스)
class Animal:
"""동물 기반 클래스"""
def __init__(self, name, age):
self.name = name
self.age = age
def speak(self):
"""소리내기 - 하위 클래스에서 구현"""
raise NotImplementedError("하위 클래스에서 구현해야 합니다.")
def move(self):
print(f"{self.name}이(가) 움직입니다.")
def info(self):
return f"{self.name} ({self.age}살)"
# 자식 클래스 (파생 클래스, 서브 클래스)
class Dog(Animal):
"""개 클래스"""
def __init__(self, name, age, breed):
# 부모 클래스의 생성자 호출
super().__init__(name, age)
self.breed = breed
def speak(self):
"""개의 짖기"""
return f"{self.name}이(가) 멍멍 짖습니다!"
def fetch(self):
"""개만의 특별한 행동"""
return f"{self.name}이(가) 공을 가져옵니다!"
class Cat(Animal):
"""고양이 클래스"""
def __init__(self, name, age, color):
super().__init__(name, age)
self.color = color
def speak(self):
"""고양이의 울음"""
return f"{self.name}이(가) 야옹 웁니다!"
def scratch(self):
"""고양이만의 특별한 행동"""
return f"{self.name}이(가) 긁적입니다!"
> [!WARNING]
> **상속을 남발하지 마세요!**
>
> 상속은 코드를 재사용하기 좋지만, 너무 깊게 상속하면 코드가 꼬입니다. (할아버지 -> 아버지 -> 나 -> 자식...)
> "A는 B이다(IS-A)" 관계가 확실할 때만 상속을 쓰세요.
> 단순히 기능을 가져다 쓰고 싶다면, 상속보다는 **포함(Composition)**을 쓰는 게 더 좋을 때가 많습니다.
# 사용 예제
dog = Dog("바둑이", 3, "진돗개")
cat = Cat("나비", 2, "검은색")
print(dog.info()) # 부모 클래스의 메서드
print(dog.speak()) # 오버라이딩된 메서드
print(dog.fetch()) # 자식 클래스의 메서드
print(cat.info())
print(cat.speak())
print(cat.scratch())
# isinstance() 확인
print(isinstance(dog, Dog)) # True
print(isinstance(dog, Animal)) # True (상속 관계)
print(isinstance(dog, Cat)) # False
다중 상속
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
# 믹스인(Mixin) 클래스들
class Flyable:
"""날 수 있는 능력"""
def fly(self):
return f"{self.name}이(가) 하늘을 날고 있습니다!"
class Swimmable:
"""수영할 수 있는 능력"""
def swim(self):
return f"{self.name}이(가) 물속을 헤엄치고 있습니다!"
# 다중 상속
class Duck(Animal, Flyable, Swimmable):
"""오리 클래스 - 다중 상속"""
def __init__(self, name, age):
super().__init__(name, age)
def speak(self):
return f"{self.name}이(가) 꽥꽥 웁니다!"
# 사용 예제
duck = Duck("도널드", 5)
print(duck.speak()) # Animal에서 상속
print(duck.fly()) # Flyable에서 상속
print(duck.swim()) # Swimmable에서 상속
# MRO (Method Resolution Order) 확인
print(Duck.__mro__)
# (<class '__main__.Duck'>, <class '__main__.Animal'>,
# <class '__main__.Flyable'>, <class '__main__.Swimmable'>, <class '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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from abc import ABC, abstractmethod
class Shape(ABC):
"""도형 추상 클래스"""
def __init__(self, name):
self.name = name
@abstractmethod
def area(self):
"""넓이 계산 - 하위 클래스에서 구현"""
pass
@abstractmethod
def perimeter(self):
"""둘레 계산 - 하위 클래스에서 구현"""
pass
def describe(self):
"""도형 설명"""
return f"{self.name}: 넓이={self.area():.2f}, 둘레={self.perimeter():.2f}"
class Rectangle(Shape):
"""사각형 클래스"""
def __init__(self, width, height):
super().__init__("사각형")
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
class Circle(Shape):
"""원 클래스"""
def __init__(self, radius):
super().__init__("원")
self.radius = radius
def area(self):
import math
return math.pi * self.radius ** 2
def perimeter(self):
import math
return 2 * math.pi * self.radius
# 사용 예제
# shape = Shape("도형") # TypeError: 추상 클래스는 인스턴스화할 수 없음
rectangle = Rectangle(5, 3)
circle = Circle(4)
shapes = [rectangle, circle]
for shape in shapes:
print(shape.describe())
🎯 다형성(Polymorphism)
메서드 오버라이딩
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 Employee:
"""직원 기반 클래스"""
def __init__(self, name, base_salary):
self.name = name
self.base_salary = base_salary
def calculate_salary(self):
"""급여 계산"""
return self.base_salary
def work(self):
return f"{self.name}님이 일하고 있습니다."
class Manager(Employee):
"""관리자 클래스"""
def __init__(self, name, base_salary, bonus):
super().__init__(name, base_salary)
self.bonus = bonus
def calculate_salary(self):
"""관리자 급여 = 기본급 + 보너스"""
return self.base_salary + self.bonus
def work(self):
return f"{self.name} 관리자님이 팀을 관리하고 있습니다."
class Developer(Employee):
"""개발자 클래스"""
def __init__(self, name, base_salary, overtime_hours):
super().__init__(name, base_salary)
self.overtime_hours = overtime_hours
self.overtime_rate = 50000 # 시간당 수당
def calculate_salary(self):
"""개발자 급여 = 기본급 + 초과근무수당"""
overtime_pay = self.overtime_hours * self.overtime_rate
return self.base_salary + overtime_pay
def work(self):
return f"{self.name} 개발자님이 코딩하고 있습니다."
# 다형성 활용
employees = [
Manager("김부장", 5000000, 1000000),
Developer("이대리", 4000000, 20),
Developer("박사원", 3500000, 30),
Employee("최인턴", 2000000)
]
# 같은 메서드 호출, 다른 동작
for emp in employees:
print(f"{emp.work()}")
print(f"급여: {emp.calculate_salary():,}원\n")
연산자 오버로딩
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
class Vector:
"""2D 벡터 클래스"""
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Vector({self.x}, {self.y})"
def __repr__(self):
return f"Vector({self.x}, {self.y})"
def __add__(self, other):
"""벡터 덧셈 (+)"""
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
return NotImplemented
def __sub__(self, other):
"""벡터 뺄셈 (-)"""
if isinstance(other, Vector):
return Vector(self.x - other.x, self.y - other.y)
return NotImplemented
def __mul__(self, scalar):
"""스칼라 곱셈 (*)"""
if isinstance(scalar, (int, float)):
return Vector(self.x * scalar, self.y * scalar)
return NotImplemented
def __rmul__(self, scalar):
"""역 스칼라 곱셈"""
return self.__mul__(scalar)
def __eq__(self, other):
"""동등 비교 (==)"""
if isinstance(other, Vector):
return self.x == other.x and self.y == other.y
return False
def __len__(self):
"""벡터의 크기 (len())"""
import math
return int(math.sqrt(self.x**2 + self.y**2))
def __getitem__(self, index):
"""인덱싱 ([])"""
if index == 0:
return self.x
elif index == 1:
return self.y
else:
raise IndexError("Vector index out of range")
def __bool__(self):
"""불리언 변환"""
return self.x != 0 or self.y != 0
# 사용 예제
v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(f"v1: {v1}")
print(f"v2: {v2}")
print(f"v1 + v2: {v1 + v2}")
print(f"v1 - v2: {v1 - v2}")
print(f"v1 * 2: {v1 * 2}")
print(f"3 * v2: {3 * v2}")
print(f"v1 == v2: {v1 == v2}")
print(f"len(v1): {len(v1)}")
print(f"v1[0]: {v1[0]}, v1[1]: {v1[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
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
class DateUtils:
"""날짜 유틸리티 클래스"""
date_format = "%Y-%m-%d" # 클래스 변수
def __init__(self, date_string):
self.date = self._parse_date(date_string)
def _parse_date(self, date_string):
"""날짜 파싱 (인스턴스 메서드)"""
from datetime import datetime
return datetime.strptime(date_string, self.date_format)
@classmethod
def from_timestamp(cls, timestamp):
"""타임스탬프로부터 객체 생성 (클래스 메서드)"""
from datetime import datetime
date_string = datetime.fromtimestamp(timestamp).strftime(cls.date_format)
return cls(date_string)
@classmethod
def change_format(cls, new_format):
"""날짜 형식 변경 (클래스 메서드)"""
cls.date_format = new_format
@staticmethod
def is_leap_year(year):
"""윤년 확인 (정적 메서드)"""
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
@staticmethod
def days_in_month(year, month):
"""월의 일수 반환 (정적 메서드)"""
if month in [1, 3, 5, 7, 8, 10, 12]:
return 31
elif month in [4, 6, 9, 11]:
return 30
elif month == 2:
return 29 if DateUtils.is_leap_year(year) else 28
else:
raise ValueError("Invalid month")
def format_date(self, format_string=None):
"""날짜 포맷팅 (인스턴스 메서드)"""
if format_string is None:
format_string = self.date_format
return self.date.strftime(format_string)
# 사용 예제
# 일반 생성자
date1 = DateUtils("2024-03-15")
print(date1.format_date())
# 클래스 메서드로 생성
import time
date2 = DateUtils.from_timestamp(time.time())
print(date2.format_date())
# 정적 메서드 사용
print(f"2024년은 윤년? {DateUtils.is_leap_year(2024)}")
print(f"2024년 2월은 {DateUtils.days_in_month(2024, 2)}일")
# 클래스 변수 변경
DateUtils.change_format("%d/%m/%Y")
date3 = DateUtils("15/03/2024")
print(date3.format_date())
💡 실전 예제
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
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
from datetime import datetime, timedelta
from typing import List, Optional
class Book:
"""도서 클래스"""
def __init__(self, isbn, title, author, published_year):
self.isbn = isbn
self.title = title
self.author = author
self.published_year = published_year
self.is_available = True
self.borrowed_by = None
self.due_date = None
def __str__(self):
status = "대출가능" if self.is_available else f"대출중 (~{self.due_date})"
return f"[{self.isbn}] {self.title} - {self.author} ({self.published_year}) [{status}]"
class Member:
"""회원 클래스"""
def __init__(self, member_id, name, email):
self.member_id = member_id
self.name = name
self.email = email
self.borrowed_books = []
self.borrow_history = []
def __str__(self):
return f"[{self.member_id}] {self.name} ({self.email}) - 대출: {len(self.borrowed_books)}권"
class Library:
"""도서관 관리 시스템"""
def __init__(self, name):
self.name = name
self.books = {} # {isbn: Book}
self.members = {} # {member_id: Member}
self.borrow_period = 14 # 대출 기간 (일)
def add_book(self, book: Book):
"""도서 추가"""
if book.isbn in self.books:
print(f"이미 등록된 도서입니다: {book.isbn}")
return False
self.books[book.isbn] = book
print(f"도서 추가 완료: {book.title}")
return True
def register_member(self, member: Member):
"""회원 등록"""
if member.member_id in self.members:
print(f"이미 등록된 회원입니다: {member.member_id}")
return False
self.members[member.member_id] = member
print(f"회원 등록 완료: {member.name}")
return True
def borrow_book(self, isbn: str, member_id: str) -> bool:
"""도서 대출"""
# 유효성 검사
if isbn not in self.books:
print(f"등록되지 않은 도서입니다: {isbn}")
return False
if member_id not in self.members:
print(f"등록되지 않은 회원입니다: {member_id}")
return False
book = self.books[isbn]
member = self.members[member_id]
if not book.is_available:
print(f"이미 대출중인 도서입니다: {book.title}")
return False
if len(member.borrowed_books) >= 5:
print(f"대출 한도(5권)를 초과했습니다.")
return False
# 대출 처리
book.is_available = False
book.borrowed_by = member_id
book.due_date = (datetime.now() + timedelta(days=self.borrow_period)).date()
member.borrowed_books.append(isbn)
member.borrow_history.append({
"isbn": isbn,
"borrow_date": datetime.now(),
"status": "borrowed"
})
print(f"대출 완료: {book.title} → {member.name} (반납일: {book.due_date})")
return True
def return_book(self, isbn: str, member_id: str) -> bool:
"""도서 반납"""
if isbn not in self.books:
print(f"등록되지 않은 도서입니다: {isbn}")
return False
book = self.books[isbn]
if book.borrowed_by != member_id:
print(f"해당 회원이 대출한 도서가 아닙니다.")
return False
member = self.members[member_id]
# 반납 처리
book.is_available = True
book.borrowed_by = None
book.due_date = None
member.borrowed_books.remove(isbn)
# 연체 확인
is_overdue = datetime.now().date() > book.due_date
# 기록 업데이트
for record in member.borrow_history:
if record["isbn"] == isbn and record["status"] == "borrowed":
record["return_date"] = datetime.now()
record["status"] = "returned"
record["overdue"] = is_overdue
break
status = "연체 반납" if is_overdue else "정상 반납"
print(f"{status}: {book.title} ← {member.name}")
return True
def search_books(self, keyword: str) -> List[Book]:
"""도서 검색"""
results = []
keyword_lower = keyword.lower()
for book in self.books.values():
if (keyword_lower in book.title.lower() or
keyword_lower in book.author.lower() or
keyword == book.isbn):
results.append(book)
return results
def get_overdue_books(self) -> List[tuple]:
"""연체 도서 목록"""
overdue = []
today = datetime.now().date()
for book in self.books.values():
if not book.is_available and book.due_date < today:
member = self.members[book.borrowed_by]
days_overdue = (today - book.due_date).days
overdue.append((book, member, days_overdue))
return overdue
def member_report(self, member_id: str) -> str:
"""회원 대출 리포트"""
if member_id not in self.members:
return "등록되지 않은 회원입니다."
member = self.members[member_id]
report = f"\n=== {member.name}님의 대출 현황 ===\n"
# 현재 대출 중인 도서
report += "\n현재 대출 중:\n"
if member.borrowed_books:
for isbn in member.borrowed_books:
book = self.books[isbn]
report += f" - {book.title} (반납일: {book.due_date})\n"
else:
report += " 없음\n"
# 대출 이력
report += "\n대출 이력:\n"
for record in member.borrow_history[-5:]: # 최근 5개
book = self.books[record["isbn"]]
status = "대출중" if record["status"] == "borrowed" else "반납완료"
if record.get("overdue"):
status += " (연체)"
report += f" - {book.title}: {record['borrow_date'].date()} [{status}]\n"
return report
# 사용 예제
library = Library("파이썬 도서관")
# 도서 추가
books = [
Book("978-89-1234-567-8", "파이썬 완벽 가이드", "김파이썬", 2023),
Book("978-89-1234-568-5", "자료구조와 알고리즘", "이알고", 2022),
Book("978-89-1234-569-2", "웹 개발 마스터", "박웹", 2024),
]
for book in books:
library.add_book(book)
# 회원 등록
members = [
Member("M001", "홍길동", "hong@email.com"),
Member("M002", "김철수", "kim@email.com"),
]
for member in members:
library.register_member(member)
# 도서 대출/반납
library.borrow_book("978-89-1234-567-8", "M001")
library.borrow_book("978-89-1234-568-5", "M001")
# 도서 검색
results = library.search_books("파이썬")
print("\n검색 결과:")
for book in results:
print(f" {book}")
# 회원 리포트
print(library.member_report("M001"))
2. 게임 캐릭터 시스템
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
from abc import ABC, abstractmethod
import random
class Character(ABC):
"""게임 캐릭터 추상 클래스"""
def __init__(self, name, hp, attack, defense):
self.name = name
self.max_hp = hp
self.hp = hp
self.attack = attack
self.defense = defense
self.level = 1
self.exp = 0
self.skills = []
@abstractmethod
def special_ability(self, target):
"""특수 능력 - 하위 클래스에서 구현"""
pass
def take_damage(self, damage):
"""데미지 받기"""
actual_damage = max(1, damage - self.defense)
self.hp -= actual_damage
print(f"{self.name}이(가) {actual_damage}의 데미지를 받았습니다! (HP: {self.hp}/{self.max_hp})")
if self.hp <= 0:
self.hp = 0
print(f"{self.name}이(가) 쓰러졌습니다!")
return False
return True
def basic_attack(self, target):
"""기본 공격"""
damage = self.attack + random.randint(-5, 5)
print(f"{self.name}의 기본 공격!")
target.take_damage(damage)
def heal(self, amount):
"""체력 회복"""
old_hp = self.hp
self.hp = min(self.max_hp, self.hp + amount)
healed = self.hp - old_hp
print(f"{self.name}이(가) {healed}만큼 회복했습니다! (HP: {self.hp}/{self.max_hp})")
def gain_exp(self, amount):
"""경험치 획득"""
self.exp += amount
print(f"{self.name}이(가) {amount} 경험치를 획득했습니다!")
# 레벨업 체크
while self.exp >= self.level * 100:
self.exp -= self.level * 100
self.level_up()
def level_up(self):
"""레벨업"""
self.level += 1
self.max_hp += 20
self.hp = self.max_hp
self.attack += 5
self.defense += 3
print(f"🎉 {self.name}이(가) 레벨 {self.level}로 레벨업했습니다!")
def __str__(self):
return (f"{self.name} (Lv.{self.level})\n"
f"HP: {self.hp}/{self.max_hp}\n"
f"공격력: {self.attack}, 방어력: {self.defense}")
class Warrior(Character):
"""전사 클래스"""
def __init__(self, name):
super().__init__(name, hp=150, attack=25, defense=15)
self.rage = 0
self.skills = ["강타", "방어태세", "광폭화"]
def special_ability(self, target):
"""전사의 특수 능력: 강력한 일격"""
print(f"{self.name}의 필살기: 강력한 일격!")
damage = self.attack * 2 + self.rage
target.take_damage(damage)
self.rage = 0 # 분노 초기화
def basic_attack(self, target):
"""전사의 기본 공격 - 분노 축적"""
super().basic_attack(target)
self.rage += 10
print(f"분노 게이지: {self.rage}")
class Mage(Character):
"""마법사 클래스"""
def __init__(self, name):
super().__init__(name, hp=80, attack=35, defense=5)
self.mana = 100
self.max_mana = 100
self.skills = ["파이어볼", "아이스 쉴드", "메테오"]
def special_ability(self, target):
"""마법사의 특수 능력: 메테오"""
if self.mana >= 50:
print(f"{self.name}의 필살기: 메테오!")
damage = self.attack * 3
target.take_damage(damage)
self.mana -= 50
print(f"마나: {self.mana}/{self.max_mana}")
else:
print("마나가 부족합니다!")
def cast_spell(self, spell_name, target):
"""주문 시전"""
spells = {
"파이어볼": (30, 20), # (마나 소모, 데미지)
"아이스볼트": (20, 15),
"라이트닝": (40, 35)
}
if spell_name in spells:
mana_cost, damage = spells[spell_name]
if self.mana >= mana_cost:
print(f"{self.name}이(가) {spell_name}을 시전합니다!")
target.take_damage(damage + self.attack)
self.mana -= mana_cost
print(f"마나: {self.mana}/{self.max_mana}")
else:
print("마나가 부족합니다!")
else:
print("알 수 없는 주문입니다!")
class Healer(Character):
"""힐러 클래스"""
def __init__(self, name):
super().__init__(name, hp=100, attack=15, defense=10)
self.mana = 150
self.max_mana = 150
self.skills = ["치유", "정화", "부활"]
def special_ability(self, target):
"""힐러의 특수 능력: 대규모 치유"""
if self.mana >= 40:
print(f"{self.name}의 특수 능력: 신성한 치유!")
heal_amount = 50
target.heal(heal_amount)
self.mana -= 40
print(f"마나: {self.mana}/{self.max_mana}")
else:
print("마나가 부족합니다!")
def heal_spell(self, target, spell_type="normal"):
"""치유 주문"""
heal_types = {
"normal": (20, 30), # (마나 소모, 치유량)
"greater": (40, 60),
"emergency": (60, 100)
}
if spell_type in heal_types:
mana_cost, heal_amount = heal_types[spell_type]
if self.mana >= mana_cost:
print(f"{self.name}이(가) {spell_type} 치유를 시전합니다!")
target.heal(heal_amount)
self.mana -= mana_cost
print(f"마나: {self.mana}/{self.max_mana}")
else:
print("마나가 부족합니다!")
# 전투 시뮬레이션
def battle_simulation():
"""전투 시뮬레이션"""
# 캐릭터 생성
warrior = Warrior("강철의 전사")
mage = Mage("대마법사")
healer = Healer("신성한 치유사")
# 적 캐릭터 (간단한 구현)
class Monster(Character):
def __init__(self, name, hp, attack, defense):
super().__init__(name, hp, attack, defense)
def special_ability(self, target):
print(f"{self.name}의 강력한 공격!")
target.take_damage(self.attack * 1.5)
dragon = Monster("고대 드래곤", 300, 40, 20)
print("=== 전투 시작! ===")
print(f"\n플레이어 팀: {warrior.name}, {mage.name}, {healer.name}")
print(f"적: {dragon.name}\n")
# 전투 라운드
round_num = 1
while dragon.hp > 0 and any(char.hp > 0 for char in [warrior, mage, healer]):
print(f"\n--- 라운드 {round_num} ---")
# 플레이어 턴
if warrior.hp > 0:
if round_num % 3 == 0:
warrior.special_ability(dragon)
else:
warrior.basic_attack(dragon)
if dragon.hp <= 0:
break
if mage.hp > 0:
if round_num % 2 == 0:
mage.special_ability(dragon)
else:
mage.cast_spell("파이어볼", dragon)
if dragon.hp <= 0:
break
if healer.hp > 0:
# 체력이 가장 낮은 아군 치유
allies = [warrior, mage, healer]
injured = min(allies, key=lambda x: x.hp if x.hp > 0 else float('inf'))
if injured.hp < injured.max_hp * 0.5:
healer.heal_spell(injured, "greater")
else:
healer.basic_attack(dragon)
if dragon.hp <= 0:
break
# 드래곤 턴
print(f"\n{dragon.name}의 차례!")
targets = [char for char in [warrior, mage, healer] if char.hp > 0]
if targets:
target = random.choice(targets)
if round_num % 4 == 0:
dragon.special_ability(target)
else:
dragon.basic_attack(target)
round_num += 1
# 전투 결과
print("\n=== 전투 종료! ===")
if dragon.hp <= 0:
print("🎉 승리! 드래곤을 물리쳤습니다!")
for char in [warrior, mage, healer]:
if char.hp > 0:
char.gain_exp(200)
else:
print("💀 패배... 파티가 전멸했습니다.")
# 실행
# battle_simulation()
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
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
from datetime import datetime
from typing import List, Optional
import uuid
class Transaction:
"""거래 내역 클래스"""
def __init__(self, transaction_type, amount, balance_after, description=""):
self.id = str(uuid.uuid4())
self.timestamp = datetime.now()
self.type = transaction_type # "deposit", "withdrawal", "transfer"
self.amount = amount
self.balance_after = balance_after
self.description = description
def __str__(self):
return (f"[{self.timestamp.strftime('%Y-%m-%d %H:%M')}] "
f"{self.type}: {self.amount:,}원 | 잔액: {self.balance_after:,}원")
class Account(ABC):
"""계좌 추상 클래스"""
def __init__(self, account_number, owner_name, initial_balance=0):
self.account_number = account_number
self.owner_name = owner_name
self._balance = initial_balance
self.transactions = []
self.is_active = True
if initial_balance > 0:
self._add_transaction("deposit", initial_balance, initial_balance, "초기 입금")
@property
def balance(self):
return self._balance
def _add_transaction(self, transaction_type, amount, balance_after, description=""):
"""거래 내역 추가"""
transaction = Transaction(transaction_type, amount, balance_after, description)
self.transactions.append(transaction)
@abstractmethod
def withdraw(self, amount):
"""출금 - 하위 클래스에서 구현"""
pass
def deposit(self, amount):
"""입금"""
if not self.is_active:
raise ValueError("비활성화된 계좌입니다.")
if amount <= 0:
raise ValueError("입금액은 0보다 커야 합니다.")
self._balance += amount
self._add_transaction("deposit", amount, self._balance)
return self._balance
def get_transaction_history(self, limit=10):
"""거래 내역 조회"""
return self.transactions[-limit:]
def __str__(self):
status = "활성" if self.is_active else "비활성"
return f"{self.__class__.__name__}({self.account_number}) - {self.owner_name}: {self._balance:,}원 [{status}]"
class SavingsAccount(Account):
"""저축 계좌"""
def __init__(self, account_number, owner_name, initial_balance=0):
super().__init__(account_number, owner_name, initial_balance)
self.interest_rate = 0.02 # 연 2%
self.withdrawal_limit = 3 # 월 출금 제한
self.monthly_withdrawals = 0
self.last_withdrawal_month = None
def withdraw(self, amount):
"""출금 (월 3회 제한)"""
if not self.is_active:
raise ValueError("비활성화된 계좌입니다.")
current_month = datetime.now().month
# 월이 바뀌면 출금 횟수 초기화
if self.last_withdrawal_month != current_month:
self.monthly_withdrawals = 0
self.last_withdrawal_month = current_month
if self.monthly_withdrawals >= self.withdrawal_limit:
raise ValueError(f"월 출금 한도({self.withdrawal_limit}회)를 초과했습니다.")
if amount <= 0:
raise ValueError("출금액은 0보다 커야 합니다.")
if amount > self._balance:
raise ValueError("잔액이 부족합니다.")
self._balance -= amount
self.monthly_withdrawals += 1
self._add_transaction("withdrawal", amount, self._balance)
return self._balance
def calculate_interest(self):
"""이자 계산 및 지급"""
interest = self._balance * self.interest_rate
self._balance += interest
self._add_transaction("deposit", interest, self._balance, "이자 지급")
return interest
class CheckingAccount(Account):
"""당좌 계좌"""
def __init__(self, account_number, owner_name, initial_balance=0, overdraft_limit=0):
super().__init__(account_number, owner_name, initial_balance)
self.overdraft_limit = overdraft_limit # 마이너스 한도
def withdraw(self, amount):
"""출금 (마이너스 통장 기능)"""
if not self.is_active:
raise ValueError("비활성화된 계좌입니다.")
if amount <= 0:
raise ValueError("출금액은 0보다 커야 합니다.")
if self._balance - amount < -self.overdraft_limit:
raise ValueError("마이너스 한도를 초과합니다.")
self._balance -= amount
self._add_transaction("withdrawal", amount, self._balance)
return self._balance
class Bank:
"""은행 시스템"""
def __init__(self, name):
self.name = name
self.accounts = {} # {account_number: Account}
self._next_account_number = 1000000
def _generate_account_number(self):
"""계좌번호 생성"""
account_number = f"{self._next_account_number:07d}"
self._next_account_number += 1
return account_number
def create_savings_account(self, owner_name, initial_balance=0):
"""저축 계좌 개설"""
account_number = self._generate_account_number()
account = SavingsAccount(account_number, owner_name, initial_balance)
self.accounts[account_number] = account
print(f"저축 계좌 개설 완료: {account}")
return account
def create_checking_account(self, owner_name, initial_balance=0, overdraft_limit=0):
"""당좌 계좌 개설"""
account_number = self._generate_account_number()
account = CheckingAccount(account_number, owner_name, initial_balance, overdraft_limit)
self.accounts[account_number] = account
print(f"당좌 계좌 개설 완료: {account}")
return account
def find_account(self, account_number) -> Optional[Account]:
"""계좌 조회"""
return self.accounts.get(account_number)
def transfer(self, from_account_number, to_account_number, amount):
"""계좌 이체"""
from_account = self.find_account(from_account_number)
to_account = self.find_account(to_account_number)
if not from_account or not to_account:
raise ValueError("유효하지 않은 계좌번호입니다.")
# 출금
from_account.withdraw(amount)
from_account._add_transaction("transfer", amount, from_account.balance,
f"이체: {to_account_number}")
# 입금
to_account.deposit(amount)
to_account._add_transaction("transfer", amount, to_account.balance,
f"이체: {from_account_number}")
print(f"이체 완료: {from_account_number} → {to_account_number}: {amount:,}원")
def get_total_deposits(self):
"""전체 예금액"""
total = sum(account.balance for account in self.accounts.values()
if account.balance > 0)
return total
def get_account_summary(self):
"""계좌 요약"""
summary = f"\n=== {self.name} 계좌 현황 ===\n"
summary += f"총 계좌 수: {len(self.accounts)}\n"
summary += f"총 예금액: {self.get_total_deposits():,}원\n\n"
for account in self.accounts.values():
summary += f"{account}\n"
return summary
# 사용 예제
bank = Bank("파이썬 은행")
# 계좌 개설
savings1 = bank.create_savings_account("김철수", 1000000)
savings2 = bank.create_savings_account("이영희", 500000)
checking = bank.create_checking_account("박민수", 200000, overdraft_limit=500000)
# 거래 실행
savings1.deposit(200000)
savings1.withdraw(50000)
checking.withdraw(300000) # 마이너스 통장 사용
# 이체
bank.transfer(savings1.account_number, checking.account_number, 100000)
# 이자 계산
interest = savings1.calculate_interest()
print(f"이자 지급: {interest:,.0f}원")
# 거래 내역 조회
print(f"\n{savings1.owner_name}님의 거래 내역:")
for transaction in savings1.get_transaction_history():
print(f" {transaction}")
# 전체 계좌 요약
print(bank.get_account_summary())
⚠️ 초보자가 자주 하는 실수
1. 클래스 변수와 인스턴스 변수 혼동
1
2
3
4
5
6
7
8
9
10
11
12
13
# ❌ 클래스 변수를 인스턴스에서 수정
class Counter:
count = 0 # 클래스 변수
def increment(self):
self.count += 1 # 새로운 인스턴스 변수 생성!
# ✅ 클래스 변수 올바른 수정
class Counter:
count = 0
def increment(self):
Counter.count += 1 # 클래스명으로 접근
2. init 메서드 실수
1
2
3
4
5
6
7
8
9
10
11
# ❌ return 값 반환
class Person:
def __init__(self, name):
self.name = name
return self # TypeError!
# ✅ __init__은 None 반환
class Person:
def __init__(self, name):
self.name = name
# return 없이 자동으로 객체 반환
3. 가변 기본값 문제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ❌ 가변 객체를 기본값으로 사용
class Student:
def __init__(self, name, grades=[]): # 위험!
self.name = name
self.grades = grades
# 모든 인스턴스가 같은 리스트를 공유!
s1 = Student("A")
s2 = Student("B")
s1.grades.append(90)
print(s2.grades) # [90] - 예상과 다름!
# ✅ None을 기본값으로 사용
class Student:
def __init__(self, name, grades=None):
self.name = name
self.grades = grades if grades is not None else []
4. super() 사용 실수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ❌ 부모 클래스 생성자 호출 안 함
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
# super().__init__(name) 빠짐!
self.breed = breed # self.name이 없음
# ✅ super() 올바른 사용
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name)
self.breed = breed
5. private 속성 오해
1
2
3
4
5
6
7
8
9
10
11
12
13
# ❌ Python에는 진짜 private이 없음
class BankAccount:
def __init__(self):
self.__balance = 0 # 네임 맹글링일 뿐
# 여전히 접근 가능
account = BankAccount()
print(account._BankAccount__balance) # 0
# ✅ 컨벤션으로 표시
class BankAccount:
def __init__(self):
self._balance = 0 # _ 하나는 "내부용" 표시
🎯 핵심 정리
OOP 설계 원칙 (SOLID)
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
# 1. 단일 책임 원칙 (SRP)
# 좋음: 각 클래스가 하나의 책임만 가짐
class EmailSender:
def send_email(self, message):
# 이메일 전송 로직
pass
class EmailValidator:
def validate_email(self, email):
# 이메일 유효성 검사
pass
# 2. 개방-폐쇄 원칙 (OCP)
# 확장에는 열려있고, 수정에는 닫혀있어야 함
class Shape(ABC):
@abstractmethod
def area(self):
pass
# 새로운 도형 추가 시 기존 코드 수정 없이 확장
class Triangle(Shape):
def area(self):
# 삼각형 넓이 계산
pass
# 3. 리스코프 치환 원칙 (LSP)
# 하위 타입은 상위 타입을 대체할 수 있어야 함
def process_bird(bird: Bird):
bird.fly() # 모든 새가 날 수 있다고 가정하면 안됨
# 4. 인터페이스 분리 원칙 (ISP)
# 클라이언트가 사용하지 않는 메서드에 의존하지 않아야 함
class Workable(ABC):
@abstractmethod
def work(self):
pass
class Eatable(ABC):
@abstractmethod
def eat(self):
pass
# 5. 의존성 역전 원칙 (DIP)
# 고수준 모듈은 저수준 모듈에 의존하면 안됨
class Database(ABC):
@abstractmethod
def save(self, data):
pass
class MySQLDatabase(Database):
def save(self, data):
# MySQL 저장 로직
pass
클래스 설계 Best Practices
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1. 적절한 추상화 레벨
class Animal: # 너무 추상적이지도, 구체적이지도 않게
pass
# 2. 명확한 이름
class UserAuthenticationService: # 역할이 명확한 이름
pass
# 3. 적절한 캡슐화
class BankAccount:
def __init__(self):
self._balance = 0 # protected
self.__pin = 1234 # private
@property
def balance(self):
return self._balance
# 4. 컴포지션 활용
class Car:
def __init__(self):
self.engine = Engine() # Has-A 관계
self.wheels = [Wheel() for _ in range(4)]
언제 OOP를 사용할까?
graph TD
A[OOP 사용 시기] --> B[복잡한 상태 관리]
A --> C[코드 재사용성]
A --> D[확장 가능성]
A --> E[팀 협업]
F[함수형 사용 시기] --> G[단순한 데이터 변환]
F --> H[상태가 없는 연산]
F --> I[병렬 처리]
🎓 파이썬 마스터하기 시리즈
📚 기초편 (1-7)
- Python 소개와 개발 환경 설정 완벽 가이드
- 변수, 자료형, 연산자 완벽 정리
- 조건문과 반복문 마스터하기
- 함수와 람다 완벽 가이드
- 리스트, 튜플, 딕셔너리 정복하기
- 문자열 처리와 정규표현식
- 파일 입출력과 예외 처리
🚀 중급편 (8-12)
💼 고급편 (13-16)
이전글: 파일 입출력과 예외 처리 ⬅️ 현재글: 클래스와 객체지향 프로그래밍 다음글: 모듈과 패키지 관리 ➡️
이번 포스트에서는 Python의 객체지향 프로그래밍을 완벽히 마스터했습니다. 다음 포스트에서는 모듈과 패키지를 관리하는 방법에 대해 알아보겠습니다. Happy Coding! 🐍✨
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.