포스트

[Python 100일 챌린지] Day 32 - 생성자와 소멸자

[Python 100일 챌린지] Day 32 - 생성자와 소멸자

Day 31에서 클래스를 만들었는데, 매번 객체 만들고 속성 설정하는 게 번거롭죠? 🤔 ──car = Car() 후에 car.color = "빨강", car.speed = 0… 이렇게 하나씩 설정하는 건 비효율적입니다! 😊

자동차 공장에서 차를 만들 때 생각해보세요. 바퀴, 엔진, 핸들을 나중에 하나씩 조립하는 게 아니라, “소나타 한 대!”라고 주문하면 완성된 차가 나오잖아요?

Python의 __init__()이 바로 이 역할을 합니다! 객체 생성과 동시에 필요한 모든 초기값을 자동으로 설정하는 “생성자”입니다! 💡

🎯 오늘의 학습 목표

⭐⭐⭐ (30-40분 완독)

📚 사전 지식


🎯 학습 목표 1: 생성자(init)의 역할 이해하기

생성자(Constructor)는 객체가 생성될 때 자동으로 호출되는 특별한 메서드입니다.

🏭 실생활 비유: 자동차 공장

자동차를 만들 때:

1
2
3
4
5
6
# 생성자 없이 (Day 31 방식)
car = Car()           # 빈 자동차 생성
car.set_info("현대", "소나타", 2024)  # 나중에 정보 설정

# 생성자 사용 (Day 32 방식)
car = Car("현대", "소나타", 2024)  # 생성과 동시에 정보 설정! ✨

장점:

  • 객체 생성 즉시 필요한 데이터 설정
  • 코드가 간결해짐
  • 잘못된 상태의 객체 생성 방지

🎯 학습 목표 2: 인스턴스 초기화 방법 배우기

init 메서드

기본 문법

1
2
3
4
5
class ClassName:
    def __init__(self, param1, param2):
        """생성자: 객체 초기화"""
        self.attribute1 = param1
        self.attribute2 = param2

특징:

  • __init__: 언더스코어 2개 + init + 언더스코어 2개
  • 객체 생성 시 자동 호출됨
  • 반환값(return)이 없음
  • 첫 번째 매개변수는 항상 self

Before & After 비교

Before (Day 31 방식)

1
2
3
4
5
6
7
8
9
10
11
12
class Dog:
    def set_info(self, name, age):
        self.name = name
        self.age = age

    def bark(self):
        return f"{self.name}: 멍멍!"

# 2단계 과정
dog = Dog()                    # 1. 객체 생성
dog.set_info("멍멍이", 3)      # 2. 정보 설정
print(dog.bark())

After (Day 32 방식) ✅

1
2
3
4
5
6
7
8
9
10
11
12
class Dog:
    def __init__(self, name, age):
        """생성자: 강아지 정보 초기화"""
        self.name = name
        self.age = age

    def bark(self):
        return f"{self.name}: 멍멍!"

# 1단계로 완료!
dog = Dog("멍멍이", 3)  # 생성과 동시에 초기화
print(dog.bark())

실행 결과:

1
멍멍이: 멍멍!

📝 생성자 활용 예제

예제 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
class Student:
    """학생 정보를 관리하는 클래스"""

    school_name = "파이썬 고등학교"

    def __init__(self, name, student_id, grade):
        """생성자: 학생 정보 초기화"""
        print(f"🎓 {name} 학생이 등록되었습니다!")
        self.name = name
        self.student_id = student_id
        self.grade = grade
        self.scores = []  # 빈 리스트로 초기화

    def add_score(self, subject, score):
        """과목 성적 추가"""
        self.scores.append({
            "subject": subject,
            "score": score
        })

    def get_average(self):
        """평균 성적 계산"""
        if not self.scores:
            return 0
        total = sum(item["score"] for item in self.scores)
        return total / len(self.scores)

    def print_report(self):
        """성적표 출력"""
        print(f"\n{'='*40}")
        print(f"학교: {Student.school_name}")
        print(f"이름: {self.name} ({self.student_id})")
        print(f"학년: {self.grade}학년")
        print(f"{'-'*40}")

        if self.scores:
            print("과목별 성적:")
            for item in self.scores:
                print(f"  - {item['subject']}: {item['score']}")
            print(f"{'-'*40}")
            print(f"평균: {self.get_average():.2f}")
        else:
            print("등록된 성적이 없습니다.")

        print(f"{'='*40}\n")

# 사용 - 훨씬 간결해졌습니다!
student1 = Student("홍길동", "2024001", 2)
student1.add_score("수학", 95)
student1.add_score("영어", 88)
student1.add_score("과학", 92)
student1.print_report()

student2 = Student("김철수", "2024002", 2)
student2.add_score("수학", 78)
student2.add_score("영어", 85)
student2.print_report()

실행 결과:

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
🎓 홍길동 학생이 등록되었습니다!
🎓 김철수 학생이 등록되었습니다!

========================================
학교: 파이썬 고등학교
이름: 홍길동 (2024001)
학년: 2학년
----------------------------------------
과목별 성적:
  - 수학: 95점
  - 영어: 88점
  - 과학: 92점
----------------------------------------
평균: 91.67점
========================================

========================================
학교: 파이썬 고등학교
이름: 김철수 (2024002)
학년: 2학년
----------------------------------------
과목별 성적:
  - 수학: 78점
  - 영어: 85점
----------------------------------------
평균: 81.50점
========================================

예제 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
class BankAccount:
    """은행 계좌를 관리하는 클래스"""

    bank_name = "파이썬 은행"
    interest_rate = 0.02  # 연 2% 이자

    def __init__(self, owner, account_number, balance=0):
        """생성자: 계좌 정보 초기화"""
        print(f"💰 {owner}님의 계좌가 개설되었습니다!")
        print(f"   계좌번호: {account_number}")
        print(f"   초기 잔액: {balance:,}\n")

        self.owner = owner
        self.account_number = account_number
        self.balance = balance
        self.transactions = []  # 거래 내역

    def deposit(self, amount):
        """입금"""
        if amount <= 0:
            return "입금액은 0보다 커야 합니다."

        self.balance += amount
        self.transactions.append(f"입금: +{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
        self.transactions.append(f"출금: -{amount:,}")
        return f"{amount:,}원이 출금되었습니다. (잔액: {self.balance:,}원)"

    def apply_interest(self):
        """이자 적용"""
        interest = int(self.balance * BankAccount.interest_rate)
        self.balance += interest
        self.transactions.append(f"이자: +{interest:,}")
        return f"이자 {interest:,}원이 적용되었습니다. (잔액: {self.balance:,}원)"

    def get_balance(self):
        """잔액 조회"""
        return f"{self.owner}님의 잔액: {self.balance:,}"

    def print_statement(self):
        """거래 내역 출력"""
        print(f"\n{'='*50}")
        print(f"{BankAccount.bank_name} - 거래 내역서")
        print(f"{'='*50}")
        print(f"예금주: {self.owner}")
        print(f"계좌번호: {self.account_number}")
        print(f"{'-'*50}")

        if self.transactions:
            print("거래 내역:")
            for i, transaction in enumerate(self.transactions, 1):
                print(f"  {i}. {transaction}")
        else:
            print("거래 내역이 없습니다.")

        print(f"{'-'*50}")
        print(f"현재 잔액: {self.balance:,}")
        print(f"{'='*50}\n")

# 사용
account1 = BankAccount("홍길동", "123-456-789", 100000)
print(account1.deposit(50000))
print(account1.withdraw(20000))
print(account1.apply_interest())
account1.print_statement()

account2 = BankAccount("김철수", "987-654-321")  # 초기 잔액 0원
print(account2.deposit(1000000))
account2.print_statement()

실행 결과:

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
💰 홍길동님의 계좌가 개설되었습니다!
   계좌번호: 123-456-789
   초기 잔액: 100,000원

💰 김철수님의 계좌가 개설되었습니다!
   계좌번호: 987-654-321
   초기 잔액: 0원

50,000원이 입금되었습니다. (잔액: 150,000원)
20,000원이 출금되었습니다. (잔액: 130,000원)
이자 2,600원이 적용되었습니다. (잔액: 132,600원)

==================================================
파이썬 은행 - 거래 내역서
==================================================
예금주: 홍길동
계좌번호: 123-456-789
--------------------------------------------------
거래 내역:
  1. 입금: +50,000원
  2. 출금: -20,000원
  3. 이자: +2,600원
--------------------------------------------------
현재 잔액: 132,600원
==================================================

1,000,000원이 입금되었습니다. (잔액: 1,000,000원)

==================================================
파이썬 은행 - 거래 내역서
==================================================
예금주: 김철수
계좌번호: 987-654-321
--------------------------------------------------
거래 내역:
  1. 입금: +1,000,000원
--------------------------------------------------
현재 잔액: 1,000,000원
==================================================

예제 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
class Character:
    """게임 캐릭터를 표현하는 클래스"""

    def __init__(self, name, char_class, level=1):
        """생성자: 캐릭터 초기화"""
        print(f"⚔️ '{name}' 캐릭터가 생성되었습니다!")
        print(f"   직업: {char_class}")
        print(f"   레벨: {level}\n")

        self.name = name
        self.char_class = char_class
        self.level = level

        # 직업별 초기 스탯 설정
        if char_class == "전사":
            self.hp = 150
            self.max_hp = 150
            self.attack = 20
        elif char_class == "마법사":
            self.hp = 100
            self.max_hp = 100
            self.attack = 30
        elif char_class == "궁수":
            self.hp = 120
            self.max_hp = 120
            self.attack = 25
        else:
            self.hp = 100
            self.max_hp = 100
            self.attack = 15

        self.exp = 0
        self.exp_to_next_level = 100

    def take_damage(self, damage):
        """피해 입기"""
        self.hp = max(0, self.hp - damage)

        if self.hp == 0:
            return f"💀 {self.name}이(가) 쓰러졌습니다!"
        else:
            return f"⚔️ {self.name}이(가) {damage}의 피해를 입었습니다. (HP: {self.hp}/{self.max_hp})"

    def heal(self, amount):
        """회복"""
        old_hp = self.hp
        self.hp = min(self.max_hp, self.hp + amount)
        healed = self.hp - old_hp
        return f"💚 {self.name}이(가) {healed}만큼 회복했습니다. (HP: {self.hp}/{self.max_hp})"

    def gain_exp(self, exp):
        """경험치 획득"""
        self.exp += exp
        message = f"{self.name}이(가) {exp} 경험치를 획득했습니다."

        # 레벨업 체크
        if self.exp >= self.exp_to_next_level:
            self.level_up()
            message += f"\n🎉 레벨 업! {self.name}의 레벨이 {self.level}이 되었습니다!"

        return message

    def level_up(self):
        """레벨업"""
        self.level += 1
        self.exp -= self.exp_to_next_level
        self.exp_to_next_level = int(self.exp_to_next_level * 1.5)

        # 스탯 증가 (직업별)
        if self.char_class == "전사":
            self.max_hp += 30
            self.attack += 3
        elif self.char_class == "마법사":
            self.max_hp += 20
            self.attack += 5
        elif self.char_class == "궁수":
            self.max_hp += 25
            self.attack += 4
        else:
            self.max_hp += 20
            self.attack += 2

        self.hp = self.max_hp

    def get_status(self):
        """상태 확인"""
        status = f"""
{'='*40}
[캐릭터 정보]
이름: {self.name}
직업: {self.char_class}
레벨: {self.level}
HP: {self.hp}/{self.max_hp}
공격력: {self.attack}
경험치: {self.exp}/{self.exp_to_next_level}
{'='*40}
        """
        return status.strip()

# 사용 - 직업별로 다른 초기 스탯!
warrior = Character("용사", "전사")
print(warrior.get_status())

print("\n" + "="*40 + "\n")

mage = Character("마법사", "마법사", level=5)
print(mage.get_status())

print("\n" + "="*40 + "\n")

archer = Character("궁수", "궁수")
print(archer.take_damage(30))
print(archer.heal(15))
print(archer.gain_exp(120))
print("\n" + archer.get_status())

실행 결과:

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
⚔️ '용사' 캐릭터가 생성되었습니다!
   직업: 전사
   레벨: 1

========================================
[캐릭터 정보]
이름: 용사
직업: 전사
레벨: 1
HP: 150/150
공격력: 20
경험치: 0/100
========================================

========================================

⚔️ '마법사' 캐릭터가 생성되었습니다!
   직업: 마법사
   레벨: 5

========================================
[캐릭터 정보]
이름: 마법사
직업: 마법사
레벨: 5
HP: 100/100
공격력: 30
경험치: 0/100
========================================

========================================

⚔️ '궁수' 캐릭터가 생성되었습니다!
   직업: 궁수
   레벨: 1

⚔️ 궁수이(가) 30의 피해를 입었습니다. (HP: 90/120)
💚 궁수이(가) 15만큼 회복했습니다. (HP: 105/120)
✨ 궁수이(가) 120 경험치를 획득했습니다.
🎉 레벨 업! 궁수의 레벨이 2이 되었습니다!

========================================
[캐릭터 정보]
이름: 궁수
직업: 궁수
레벨: 2
HP: 145/145
공격력: 29
경험치: 20/150
========================================

🎨 기본값을 가진 생성자

생성자 매개변수에 기본값을 설정할 수 있습니다.

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 Product:
    """상품 정보를 관리하는 클래스"""

    def __init__(self, name, price, quantity=1, category="기타"):
        """생성자: 상품 정보 초기화"""
        self.name = name
        self.price = price
        self.quantity = quantity
        self.category = category

    def get_total_price(self):
        """총 가격 계산"""
        return self.price * self.quantity

    def print_info(self):
        """상품 정보 출력"""
        print(f"{'='*40}")
        print(f"상품명: {self.name}")
        print(f"카테고리: {self.category}")
        print(f"단가: {self.price:,}")
        print(f"수량: {self.quantity}")
        print(f"총 가격: {self.get_total_price():,}")
        print(f"{'='*40}\n")

# 다양한 방식으로 생성 가능
product1 = Product("노트북", 1500000, 2, "전자제품")
product1.print_info()

product2 = Product("마우스", 30000)  # quantity=1, category="기타"
product2.print_info()

product3 = Product("키보드", 80000, category="전자제품")  # quantity=1
product3.print_info()

실행 결과:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
========================================
상품명: 노트북
카테고리: 전자제품
단가: 1,500,000원
수량: 2개
총 가격: 3,000,000원
========================================

========================================
상품명: 마우스
카테고리: 기타
단가: 30,000원
수량: 1개
총 가격: 30,000원
========================================

========================================
상품명: 키보드
카테고리: 전자제품
단가: 80,000원
수량: 1개
총 가격: 80,000원
========================================

🎯 학습 목표 3: 소멸자(del)의 동작 원리 알기

소멸자(Destructor)는 객체가 메모리에서 삭제될 때 자동으로 호출되는 메서드입니다.

기본 문법

1
2
3
4
5
6
7
8
9
class ClassName:
    def __init__(self, name):
        """생성자"""
        self.name = name
        print(f"{self.name} 생성")

    def __del__(self):
        """소멸자"""
        print(f"💀 {self.name} 소멸")

소멸자 동작 예시

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 FileHandler:
    """파일 핸들러 클래스"""

    def __init__(self, filename):
        """생성자: 파일 정보 설정"""
        self.filename = filename
        print(f"📂 {filename} 핸들러 생성")

    def __del__(self):
        """소멸자: 리소스 정리"""
        print(f"🗑️ {self.filename} 핸들러 정리 중...")
        print(f"   - 파일 연결 종료")
        print(f"   - 메모리 해제")

# 사용
print("프로그램 시작")
handler1 = FileHandler("data.txt")
handler2 = FileHandler("config.json")

print("\n작업 수행 중...\n")

# 명시적 삭제
del handler1

print("\n프로그램 종료")
# 프로그램 종료 시 handler2 자동 삭제

실행 결과:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
프로그램 시작
📂 data.txt 핸들러 생성
📂 config.json 핸들러 생성

작업 수행 중...

🗑️ data.txt 핸들러 정리 중...
   - 파일 연결 종료
   - 메모리 해제

프로그램 종료
🗑️ config.json 핸들러 정리 중...
   - 파일 연결 종료
   - 메모리 해제

소멸자 실전 예제: 데이터베이스 연결

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
class DatabaseConnection:
    """데이터베이스 연결을 관리하는 클래스"""

    connection_count = 0

    def __init__(self, host, database):
        """생성자: 데이터베이스 연결"""
        self.host = host
        self.database = database
        self.is_connected = True
        DatabaseConnection.connection_count += 1

        print(f"🔗 데이터베이스 연결 성공!")
        print(f"   호스트: {self.host}")
        print(f"   데이터베이스: {self.database}")
        print(f"   현재 연결 수: {DatabaseConnection.connection_count}\n")

    def query(self, sql):
        """쿼리 실행"""
        if not self.is_connected:
            return "❌ 연결이 끊어졌습니다."
        return f"✅ 쿼리 실행: {sql}"

    def close(self):
        """연결 종료"""
        if self.is_connected:
            self.is_connected = False
            DatabaseConnection.connection_count -= 1
            print(f"🔌 {self.database} 연결 종료")

    def __del__(self):
        """소멸자: 리소스 정리"""
        if self.is_connected:
            print(f"⚠️ 자동 정리: {self.database} 연결 종료")
            DatabaseConnection.connection_count -= 1
        print(f"   현재 연결 수: {DatabaseConnection.connection_count}")

# 사용
print("=" * 50)
db1 = DatabaseConnection("localhost", "users_db")
print(db1.query("SELECT * FROM users"))

db2 = DatabaseConnection("localhost", "products_db")
print(db2.query("SELECT * FROM products"))

print("\n" + "=" * 50)
print("db1 명시적 종료")
print("=" * 50 + "\n")
db1.close()
del db1

print("\n" + "=" * 50)
print("프로그램 종료 (db2 자동 정리)")
print("=" * 50 + "\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
==================================================
🔗 데이터베이스 연결 성공!
   호스트: localhost
   데이터베이스: users_db
   현재 연결 수: 1

✅ 쿼리 실행: SELECT * FROM users
🔗 데이터베이스 연결 성공!
   호스트: localhost
   데이터베이스: products_db
   현재 연결 수: 2

✅ 쿼리 실행: SELECT * FROM products

==================================================
db1 명시적 종료
==================================================

🔌 users_db 연결 종료
   현재 연결 수: 1

==================================================
프로그램 종료 (db2 자동 정리)
==================================================

⚠️ 자동 정리: products_db 연결 종료
   현재 연결 수: 0

🎯 학습 목표 4: 생성자와 소멸자 활용하기

소멸자 사용 시 주의사항

1. 소멸자는 예측 불가능

Python의 가비지 컬렉터가 언제 소멸자를 호출할지 보장할 수 없습니다.

1
2
3
4
5
6
7
8
9
10
11
12
class Resource:
    def __init__(self, name):
        self.name = name
        print(f"{name} 생성")

    def __del__(self):
        print(f"💀 {name} 소멸")  # 언제 호출될지 모름!

# 소멸 시점을 정확히 예측할 수 없음
res1 = Resource("리소스1")
res2 = Resource("리소스2")
# 프로그램 종료 시점에 어떤 순서로 소멸될지 모름

2. 명시적 정리 메서드 사용 권장 ✅

리소스 정리가 중요한 경우, 소멸자 대신 명시적 메서드를 사용하세요!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class FileManager:
    def __init__(self, filename):
        self.filename = filename
        print(f"📂 {filename} 열기")

    def close(self):  # ✅ 명시적 정리 메서드
        """파일 닫기"""
        print(f"🔒 {self.filename} 닫기")

    def __del__(self):
        """소멸자: 백업 정리"""
        print(f"🗑️ {self.filename} 소멸자 호출 (백업)")

# 사용
manager = FileManager("data.txt")
# ... 작업 ...
manager.close()  # ✅ 명시적으로 정리

3. Context Manager 사용 (권장) ⭐

Python의 with 문과 함께 사용하면 자동으로 정리됩니다. (Day 55에서 자세히 배웁니다!)

1
2
3
4
# 미리보기 (나중에 자세히!)
with open("data.txt", "r") as file:
    content = file.read()
# with 블록 종료 시 자동으로 파일 닫힘!

💡 실전 팁 & 주의사항

Tip 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
# ❌ 나쁜 예
class DataProcessor:
    def __init__(self, filename):
        # 생성자에서 복잡한 작업 수행
        with open(filename) as f:  # 파일이 없으면 에러!
            self.data = f.read()
        self.process_data()  # 시간이 오래 걸림

# ✅ 좋은 예
class DataProcessor:
    def __init__(self, filename):
        # 생성자는 간단하게
        self.filename = filename
        self.data = None

    def load_data(self):
        # 별도 메서드로 분리
        with open(self.filename) as f:
            self.data = f.read()

    def process_data(self):
        # 별도 메서드로 분리
        if self.data:
            # 데이터 처리
            pass

Tip 2: 소멸자보다 명시적 정리 메서드 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# ✅ 권장하는 방식
class DatabaseConnection:
    def __init__(self, host, port):
        self.host = host
        self.port = port
        self.connected = False

    def connect(self):
        """명시적 연결"""
        self.connected = True
        print(f"{self.host}:{self.port} 연결")

    def disconnect(self):
        """명시적 종료"""
        self.connected = False
        print(f"{self.host}:{self.port} 연결 종료")

# 사용
db = DatabaseConnection("localhost", 5432)
db.connect()
# 작업 수행
db.disconnect()  # 명시적으로 정리

Tip 3: 기본값은 불변 객체로

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# ❌ 위험한 예 - 가변 객체를 기본값으로
class Student:
    def __init__(self, name, scores=[]):  # ❌ 모든 인스턴스가 같은 리스트 공유!
        self.name = name
        self.scores = scores

s1 = Student("홍길동")
s1.scores.append(90)

s2 = Student("김철수")
print(s2.scores)  # [90] - 원치 않는 공유 발생!

# ✅ 올바른 예
class Student:
    def __init__(self, name, scores=None):  # None 사용
        self.name = name
        self.scores = scores if scores is not None else []

s1 = Student("홍길동")
s1.scores.append(90)

s2 = Student("김철수")
print(s2.scores)  # [] - 독립적인 리스트

🧪 연습 문제

문제 1: 직원 관리 클래스

다음 요구사항을 만족하는 Employee 클래스를 작성하세요:

요구사항:

  1. 생성자로 이름, 부서, 연봉 초기화
  2. 연봉 인상 메서드 (인상률 %)
  3. 직원 정보 출력 메서드
  4. 클래스 속성으로 총 직원 수 관리
1
2
3
4
5
6
7
8
9
# 사용 예시
emp1 = Employee("홍길동", "개발팀", 50000000)
emp2 = Employee("김철수", "디자인팀", 45000000)

emp1.print_info()
emp1.raise_salary(10)
emp1.print_info()

print(f"\n총 직원 수: {Employee.get_employee_count()}")

예상 출력:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
✨ 홍길동님이 입사했습니다!
✨ 김철수님이 입사했습니다!

========================================
이름: 홍길동
부서: 개발팀
연봉: 50,000,000원
========================================
💰 연봉이 10% 인상되었습니다!
========================================
이름: 홍길동
부서: 개발팀
연봉: 55,000,000원
========================================

총 직원 수: 2명
💡 힌트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Employee:
    employee_count = 0  # 클래스 속성

    def __init__(self, name, department, salary):
        # 인스턴스 속성 초기화
        # employee_count 증가
        pass

    def raise_salary(self, rate):
        # 연봉 = 연봉 * (1 + rate/100)
        pass

    def print_info(self):
        # 직원 정보 출력
        pass

    @classmethod
    def get_employee_count(cls):
        return cls.employee_count
✅ 정답
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
class Employee:
    """직원 정보를 관리하는 클래스"""

    employee_count = 0  # 클래스 속성

    def __init__(self, name, department, salary):
        """생성자: 직원 정보 초기화"""
        print(f"{name}님이 입사했습니다!")
        self.name = name
        self.department = department
        self.salary = salary
        Employee.employee_count += 1

    def raise_salary(self, rate):
        """연봉 인상"""
        self.salary = int(self.salary * (1 + rate / 100))
        print(f"💰 연봉이 {rate}% 인상되었습니다!")

    def print_info(self):
        """직원 정보 출력"""
        print("=" * 40)
        print(f"이름: {self.name}")
        print(f"부서: {self.department}")
        print(f"연봉: {self.salary:,}")
        print("=" * 40)

    @classmethod
    def get_employee_count(cls):
        """총 직원 수 반환"""
        return cls.employee_count

# 테스트
emp1 = Employee("홍길동", "개발팀", 50000000)
emp2 = Employee("김철수", "디자인팀", 45000000)

print()
emp1.print_info()
emp1.raise_salary(10)
emp1.print_info()

print(f"\n총 직원 수: {Employee.get_employee_count()}")

문제 2: 도서 대여 시스템

다음 요구사항을 만족하는 Library 클래스를 작성하세요:

요구사항:

  1. 생성자로 도서관 이름 초기화, 도서 목록은 빈 리스트
  2. 도서 추가 메서드
  3. 도서 대여 메서드 (대여 가능 여부 체크)
  4. 도서 반납 메서드
  5. 전체 도서 목록 출력 메서드
1
2
3
4
5
6
7
8
9
10
# 사용 예시
library = Library("파이썬 도서관")
library.add_book("파이썬 정복", "홍길동")
library.add_book("자료구조", "김철수")

library.print_books()
library.borrow_book("파이썬 정복")
library.print_books()
library.return_book("파이썬 정복")
library.print_books()
💡 힌트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Library:
    def __init__(self, name):
        self.name = name
        self.books = []  # [{"title": "...", "author": "...", "available": True}, ...]

    def add_book(self, title, author):
        # 도서 추가 (기본값 available=True)
        pass

    def borrow_book(self, title):
        # 도서 찾기
        # available이 True면 False로 변경
        pass

    def return_book(self, title):
        # 도서 찾기
        # available을 True로 변경
        pass

    def print_books(self):
        # 도서 목록 출력
        pass
✅ 정답
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
class Library:
    """도서관을 관리하는 클래스"""

    def __init__(self, name):
        """생성자: 도서관 초기화"""
        print(f"📚 '{name}' 도서관이 개관했습니다!\n")
        self.name = name
        self.books = []

    def add_book(self, title, author):
        """도서 추가"""
        self.books.append({
            "title": title,
            "author": author,
            "available": True
        })
        print(f"'{title}' 도서가 추가되었습니다.")

    def borrow_book(self, title):
        """도서 대여"""
        for book in self.books:
            if book["title"] == title:
                if book["available"]:
                    book["available"] = False
                    print(f"📖 '{title}' 도서를 대여했습니다.")
                    return
                else:
                    print(f"'{title}' 도서는 이미 대여 중입니다.")
                    return
        print(f"'{title}' 도서를 찾을 수 없습니다.")

    def return_book(self, title):
        """도서 반납"""
        for book in self.books:
            if book["title"] == title:
                if not book["available"]:
                    book["available"] = True
                    print(f"'{title}' 도서를 반납했습니다.")
                    return
                else:
                    print(f"'{title}' 도서는 대여 중이 아닙니다.")
                    return
        print(f"'{title}' 도서를 찾을 수 없습니다.")

    def print_books(self):
        """도서 목록 출력"""
        print(f"\n{'='*50}")
        print(f"{self.name} - 도서 목록")
        print(f"{'='*50}")

        if not self.books:
            print("등록된 도서가 없습니다.")
        else:
            for i, book in enumerate(self.books, 1):
                status = "✅ 대여 가능" if book["available"] else "❌ 대여 중"
                print(f"{i}. {book['title']} ({book['author']}) - {status}")

        print(f"{'='*50}\n")

# 테스트
library = Library("파이썬 도서관")
library.add_book("파이썬 정복", "홍길동")
library.add_book("자료구조", "김철수")
library.add_book("알고리즘", "이영희")

library.print_books()
library.borrow_book("파이썬 정복")
library.borrow_book("파이썬 정복")  # 이미 대여 중
library.print_books()
library.return_book("파이썬 정복")
library.print_books()

📝 오늘 배운 내용 정리

개념 설명 예시
__init__ 생성자, 객체 초기화 def __init__(self, name):
__del__ 소멸자, 객체 정리 def __del__(self):
기본값 매개변수 생성자 매개변수 기본값 def __init__(self, name, age=0):
자동 호출 생성/소멸 시 자동 실행 명시적 호출 불필요

생성자와 소멸자 비교

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Example:
    def __init__(self, name):
        """생성자: 객체 생성 시 자동 호출"""
        self.name = name
        print(f"{name} 생성")

    def __del__(self):
        """소멸자: 객체 삭제 시 자동 호출"""
        print(f"💀 {self.name} 소멸")

# 생성자 자동 호출
obj = Example("테스트")  # ✨ 테스트 생성

# 소멸자 자동 호출
del obj  # 💀 테스트 소멸

베스트 프랙티스

생성자 사용:

  • 객체 생성 시 필수 데이터 초기화
  • 기본값 제공으로 유연성 확보
  • 초기화 로직을 생성자에 집중

소멸자 사용:

  • 리소스 정리가 필요한 경우에만 사용
  • 가능하면 명시적 정리 메서드 제공 (close(), cleanup() 등)
  • Context Manager(with 문) 사용 권장

피해야 할 패턴:

  • 생성자에서 복잡한 로직 수행
  • 소멸자에 의존적인 리소스 관리
  • 생성자에서 예외 발생 가능한 작업

🔗 관련 자료

📚 이전 학습

Day 31: 클래스와 객체 ⭐⭐⭐

어제는 객체지향 프로그래밍의 기초인 클래스와 객체의 개념을 배웠습니다!

📚 다음 학습

Day 33: 인스턴스 메서드 ⭐⭐⭐

내일은 인스턴스 메서드를 심화적으로 배우고 메서드를 활용하는 방법을 익힙니다!


“늦었다고 생각할 때가 가장 빠른 시기입니다!” 🚀

Day 32/100 Phase 4: 객체지향 프로그래밍 #100DaysOfPython
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.