포스트

[Python 100일 챌린지] Day 33 - 인스턴스 변수와 메서드

[Python 100일 챌린지] Day 33 - 인스턴스 변수와 메서드

객체에 데이터만 저장하면 되나요? 그럼 딕셔너리랑 뭐가 다를까요? 🤔 ──진짜 차이는 “행동(메서드)”을 가질 수 있다는 점입니다! 😊

게임 캐릭터를 생각해보세요. HP, 공격력 같은 속성만 있으면 아무것도 못 합니다. attack(), defend(), heal() 같은 행동이 있어야 진짜 캐릭터죠!

자동차도 마찬가지입니다. 색상, 속도 같은 데이터만 있으면 주차장에 세워진 차와 같습니다. drive(), stop(), accelerate() 같은 메서드가 있어야 움직이는 차가 됩니다!

오늘은 객체에 “생명”을 불어넣는 인스턴스 메서드를 배웁니다! 💡

🎯 오늘의 학습 목표

⭐⭐⭐ (35-45분 완독)

📚 사전 지식


🎯 학습 목표 1: 인스턴스 메서드의 개념 이해하기

인스턴스 변수 vs 인스턴스 메서드

인스턴스 변수 (Instance Variable)

각 객체가 독립적으로 가지는 데이터입니다.

1
2
3
4
5
6
7
8
9
10
class Student:
    def __init__(self, name, age):
        self.name = name  # 인스턴스 변수
        self.age = age    # 인스턴스 변수

student1 = Student("홍길동", 20)
student2 = Student("김철수", 22)

print(student1.name, student1.age)  # 홍길동 20
print(student2.name, student2.age)  # 김철수 22

인스턴스 메서드 (Instance Method)

각 객체가 수행할 수 있는 동작입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Student:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def introduce(self):  # 인스턴스 메서드
        return f"안녕하세요, 저는 {self.name}이고 {self.age}살입니다."

    def is_adult(self):  # 인스턴스 메서드
        return self.age >= 20

student = Student("홍길동", 20)
print(student.introduce())  # 안녕하세요, 저는 홍길동이고 20살입니다.
print(student.is_adult())   # True

🎯 학습 목표 2: self 매개변수의 역할 알기

Private 변수와 Name Mangling

Python에서 진정한 private은 없지만, 네이밍 규칙으로 접근을 제한할 수 있습니다.

1. Public 변수 (기본)

누구나 접근 가능한 변수입니다.

1
2
3
4
5
6
7
8
class BankAccount:
    def __init__(self, balance):
        self.balance = balance  # Public 변수

account = BankAccount(10000)
print(account.balance)  # 10000 (직접 접근 가능)
account.balance = -5000 # ❌ 음수로 변경 가능! (위험)
print(account.balance)  # -5000

2. Protected 변수 (언더스코어 1개: _variable)

“외부에서 직접 접근하지 말아주세요”라는 약속입니다.

1
2
3
4
5
6
class BankAccount:
    def __init__(self, balance):
        self._balance = balance  # Protected 변수

account = BankAccount(10000)
print(account._balance)  # 10000 (기술적으로는 가능하지만 권장하지 않음)

💡 관례: _로 시작하는 변수는 “내부용”이라는 신호입니다.

3. Private 변수 (언더스코어 2개: __variable)

Name Mangling으로 접근을 어렵게 만듭니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class BankAccount:
    def __init__(self, balance):
        self.__balance = balance  # Private 변수

    def get_balance(self):
        return self.__balance

    def deposit(self, amount):
        if amount > 0:
            self.__balance += amount

account = BankAccount(10000)
print(account.get_balance())  # 10000 (메서드로 접근)

# 직접 접근 시도
print(account.__balance)  # AttributeError!

실행 결과:

1
2
3
4
5
10000
Traceback (most recent call last):
  File "...", line X, in <module>
    print(account.__balance)
AttributeError: 'BankAccount' object has no attribute '__balance'

Name Mangling 동작 원리

Python은 __변수명_클래스명__변수명으로 변환합니다.

1
2
3
4
5
6
7
8
9
10
11
12
class BankAccount:
    def __init__(self, balance):
        self.__balance = balance

account = BankAccount(10000)

# 실제 변수명 확인
print(dir(account))
# [..., '_BankAccount__balance', ...]

# 변환된 이름으로 접근 (권장하지 않음!)
print(account._BankAccount__balance)  # 10000

💡 하지만 이렇게 접근하지 마세요! Private의 의도를 무시하는 행동입니다.


🎯 학습 목표 3: 인스턴스 메서드 정의하고 호출하기

Getter와 Setter 메서드

Private 변수에 안전하게 접근하기 위한 패턴입니다.

전통적인 방식

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
class BankAccount:
    def __init__(self, owner, balance):
        self.__owner = owner
        self.__balance = balance

    # Getter 메서드
    def get_balance(self):
        """잔액 조회"""
        return self.__balance

    def get_owner(self):
        """예금주 조회"""
        return self.__owner

    # Setter 메서드
    def set_balance(self, balance):
        """잔액 설정 (유효성 검사 포함)"""
        if balance < 0:
            print("❌ 잔액은 음수가 될 수 없습니다.")
            return
        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:,}원)"

# 사용
account = BankAccount("홍길동", 10000)
print(account.get_owner())      # 홍길동
print(account.get_balance())    # 10000
print(account.deposit(5000))    # ✅ 5,000원 입금 완료
print(account.withdraw(3000))   # ✅ 3,000원 출금 완료

# 직접 수정 시도 (불가능)
# account.__balance = -1000  # AttributeError!

# Setter로 음수 방지
account.set_balance(-5000)  # ❌ 잔액은 음수가 될 수 없습니다.

실행 결과:

1
2
3
4
5
홍길동
10000
✅ 5,000원 입금 완료 (잔액: 15,000원)
✅ 3,000원 출금 완료 (잔액: 12,000원)
❌ 잔액은 음수가 될 수 없습니다.

⭐ @property 데코레이터

Python의 @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
27
28
29
30
31
32
33
34
35
36
37
38
39
class Person:
    def __init__(self, name, age):
        self.__name = name
        self.__age = age

    @property
    def name(self):
        """Getter: 이름 조회"""
        return self.__name

    @property
    def age(self):
        """Getter: 나이 조회"""
        return self.__age

    @age.setter
    def age(self, value):
        """Setter: 나이 설정 (유효성 검사)"""
        if value < 0:
            print("❌ 나이는 음수가 될 수 없습니다.")
            return
        if value > 150:
            print("❌ 나이가 너무 많습니다.")
            return
        self.__age = value

# 사용 - 메서드가 아닌 속성처럼!
person = Person("홍길동", 30)

# Getter (괄호 없이!)
print(person.name)  # 홍길동
print(person.age)   # 30

# Setter (= 사용!)
person.age = 31
print(person.age)   # 31

person.age = -5     # ❌ 나이는 음수가 될 수 없습니다.
person.age = 200    # ❌ 나이가 너무 많습니다.

실행 결과:

1
2
3
4
5
홍길동
30
31
❌ 나이는 음수가 될 수 없습니다.
❌ 나이가 너무 많습니다.

Before & After 비교

Before (전통적 방식)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class BankAccount:
    def __init__(self, balance):
        self.__balance = balance

    def get_balance(self):
        return self.__balance

    def set_balance(self, value):
        if value >= 0:
            self.__balance = value

account = BankAccount(10000)
print(account.get_balance())  # 메서드 호출
account.set_balance(20000)    # 메서드 호출

After (@property 사용) ✅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class BankAccount:
    def __init__(self, balance):
        self.__balance = balance

    @property
    def balance(self):
        return self.__balance

    @balance.setter
    def balance(self, value):
        if value >= 0:
            self.__balance = value

account = BankAccount(10000)
print(account.balance)  # 속성 접근!
account.balance = 20000 # 속성 할당!

🎯 학습 목표 4: 메서드로 객체 상태 변경하기

실전 예제

예제 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
class Temperature:
    """온도를 관리하는 클래스 (자동 변환)"""

    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:
            print("❌ 절대영도(-273.15°C) 이하는 불가능합니다.")
            return
        self.__celsius = value

    @property
    def fahrenheit(self):
        """Getter: 화씨 온도 (자동 계산)"""
        return self.__celsius * 9/5 + 32

    @fahrenheit.setter
    def fahrenheit(self, value):
        """Setter: 화씨 온도 (섭씨로 변환 저장)"""
        celsius = (value - 32) * 5/9
        if celsius < -273.15:
            print("❌ 절대영도 이하는 불가능합니다.")
            return
        self.__celsius = celsius

    @property
    def kelvin(self):
        """Getter: 켈빈 온도 (자동 계산)"""
        return self.__celsius + 273.15

    @kelvin.setter
    def kelvin(self, value):
        """Setter: 켈빈 온도 (섭씨로 변환 저장)"""
        if value < 0:
            print("❌ 켈빈 온도는 0 이상이어야 합니다.")
            return
        self.__celsius = value - 273.15

    def print_all(self):
        """모든 온도 출력"""
        print(f"{'='*40}")
        print(f"섭씨: {self.celsius:.2f}°C")
        print(f"화씨: {self.fahrenheit:.2f}°F")
        print(f"켈빈: {self.kelvin:.2f}K")
        print(f"{'='*40}\n")

# 사용
temp = Temperature(25)
temp.print_all()

# 화씨로 설정하면 섭씨도 자동 변환!
temp.fahrenheit = 98.6
temp.print_all()

# 켈빈으로 설정
temp.kelvin = 300
temp.print_all()

# 유효성 검사
temp.celsius = -300  # ❌ 절대영도 이하는 불가능합니다.

실행 결과:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
========================================
섭씨: 25.00°C
화씨: 77.00°F
켈빈: 298.15K
========================================

========================================
섭씨: 37.00°C
화씨: 98.60°F
켈빈: 310.15K
========================================

========================================
섭씨: 26.85°C
화씨: 80.33°F
켈빈: 300.00K
========================================

❌ 절대영도(-273.15°C) 이하는 불가능합니다.

예제 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
class Rectangle:
    """사각형을 표현하는 클래스"""

    def __init__(self, width, height):
        """생성자: 가로, 세로 초기화"""
        self.__width = width
        self.__height = height

    @property
    def width(self):
        """Getter: 가로"""
        return self.__width

    @width.setter
    def width(self, value):
        """Setter: 가로 (양수 체크)"""
        if value <= 0:
            print("❌ 가로 길이는 0보다 커야 합니다.")
            return
        self.__width = value

    @property
    def height(self):
        """Getter: 세로"""
        return self.__height

    @height.setter
    def height(self, value):
        """Setter: 세로 (양수 체크)"""
        if value <= 0:
            print("❌ 세로 길이는 0보다 커야 합니다.")
            return
        self.__height = value

    @property
    def area(self):
        """Getter: 넓이 (자동 계산, read-only)"""
        return self.__width * self.__height

    @property
    def perimeter(self):
        """Getter: 둘레 (자동 계산, read-only)"""
        return 2 * (self.__width + self.__height)

    def print_info(self):
        """사각형 정보 출력"""
        print(f"{'='*40}")
        print(f"가로: {self.width}")
        print(f"세로: {self.height}")
        print(f"넓이: {self.area}")
        print(f"둘레: {self.perimeter}")
        print(f"{'='*40}\n")

# 사용
rect = Rectangle(5, 3)
rect.print_info()

# 가로 변경 - 넓이/둘레 자동 업데이트!
rect.width = 10
rect.print_info()

# read-only 속성 변경 시도
try:
    rect.area = 100  # AttributeError!
except AttributeError as e:
    print(f"❌ 에러: {e}\n")

# 유효성 검사
rect.width = -5  # ❌ 가로 길이는 0보다 커야 합니다.

실행 결과:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
========================================
가로: 5
세로: 3
넓이: 15
둘레: 16
========================================

========================================
가로: 10
세로: 3
넓이: 30
둘레: 26
========================================

❌ 에러: can't set attribute

❌ 가로 길이는 0보다 커야 합니다.

예제 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
class Employee:
    """직원 정보를 관리하는 클래스"""

    def __init__(self, name, monthly_salary):
        """생성자: 이름, 월급 초기화"""
        self.__name = name
        self.__monthly_salary = monthly_salary

    @property
    def name(self):
        """Getter: 이름 (read-only)"""
        return self.__name

    @property
    def monthly_salary(self):
        """Getter: 월급"""
        return self.__monthly_salary

    @monthly_salary.setter
    def monthly_salary(self, value):
        """Setter: 월급 (양수 체크)"""
        if value <= 0:
            print("❌ 월급은 0보다 커야 합니다.")
            return
        self.__monthly_salary = value
        print(f"{self.__name}님의 월급이 {value:,}원으로 변경되었습니다.")

    @property
    def annual_salary(self):
        """Getter: 연봉 (자동 계산)"""
        return self.__monthly_salary * 12

    @property
    def weekly_salary(self):
        """Getter: 주급 (자동 계산)"""
        return self.__monthly_salary // 4

    def raise_salary(self, rate):
        """월급 인상 (퍼센트)"""
        if rate <= 0:
            print("❌ 인상률은 0보다 커야 합니다.")
            return

        old_salary = self.__monthly_salary
        self.__monthly_salary = int(self.__monthly_salary * (1 + rate / 100))
        increase = self.__monthly_salary - old_salary

        print(f"💰 {self.__name}님의 월급이 {rate}% 인상되었습니다!")
        print(f"   이전: {old_salary:,}")
        print(f"   현재: {self.__monthly_salary:,}")
        print(f"   인상액: {increase:,}")

    def print_info(self):
        """직원 정보 출력"""
        print(f"\n{'='*50}")
        print(f"이름: {self.name}")
        print(f"{'-'*50}")
        print(f"월급: {self.monthly_salary:,}")
        print(f"주급: {self.weekly_salary:,}")
        print(f"연봉: {self.annual_salary:,}")
        print(f"{'='*50}\n")

# 사용
emp = Employee("홍길동", 3000000)
emp.print_info()

# 월급 인상
emp.raise_salary(10)
emp.print_info()

# 월급 직접 변경 (setter 호출)
emp.monthly_salary = 3500000
emp.print_info()

# 이름 변경 시도 (read-only)
try:
    emp.name = "김철수"  # AttributeError!
except AttributeError 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
23
24
25
26
27
28
29
30
31
32
==================================================
이름: 홍길동
--------------------------------------------------
월급: 3,000,000원
주급: 750,000원
연봉: 36,000,000원
==================================================

💰 홍길동님의 월급이 10% 인상되었습니다!
   이전: 3,000,000원
   현재: 3,300,000원
   인상액: 300,000원

==================================================
이름: 홍길동
--------------------------------------------------
월급: 3,300,000원
주급: 825,000원
연봉: 39,600,000원
==================================================

✅ 홍길동님의 월급이 3,500,000원으로 변경되었습니다.

==================================================
이름: 홍길동
--------------------------------------------------
월급: 3,500,000원
주급: 875,000원
연봉: 42,000,000원
==================================================

❌ 에러: can't set attribute

📊 캡슐화와 정보 은닉

캡슐화(Encapsulation)는 데이터와 메서드를 하나로 묶고, 외부 접근을 제한하는 개념입니다.

캡슐화의 장점

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
class Password:
    """비밀번호를 안전하게 관리하는 클래스"""

    def __init__(self, password):
        """생성자: 비밀번호 초기화"""
        self.__password = self.__hash_password(password)

    def __hash_password(self, password):
        """Private 메서드: 비밀번호 해싱 (간단한 예시)"""
        # 실제로는 hashlib.sha256 등 사용
        return f"hashed_{password}"

    def check_password(self, input_password):
        """비밀번호 확인"""
        hashed = self.__hash_password(input_password)
        return hashed == self.__password

    def change_password(self, old_password, new_password):
        """비밀번호 변경"""
        if not self.check_password(old_password):
            return "❌ 현재 비밀번호가 일치하지 않습니다."

        if len(new_password) < 8:
            return "❌ 새 비밀번호는 8자 이상이어야 합니다."

        self.__password = self.__hash_password(new_password)
        return "✅ 비밀번호가 변경되었습니다."

# 사용
pwd = Password("mysecret123")

# 비밀번호 확인
print(pwd.check_password("mysecret123"))  # True
print(pwd.check_password("wrong"))        # False

# 비밀번호 변경
print(pwd.change_password("wrong", "newpass123"))     # ❌ 현재 비밀번호 불일치
print(pwd.change_password("mysecret123", "short"))    # ❌ 8자 미만
print(pwd.change_password("mysecret123", "newpass123"))  # ✅ 변경 성공

# 직접 접근 불가 (보안!)
# print(pwd.__password)  # AttributeError!

실행 결과:

1
2
3
4
5
True
False
❌ 현재 비밀번호가 일치하지 않습니다.
❌ 새 비밀번호는 8자 이상이어야 합니다.
✅ 비밀번호가 변경되었습니다.

💡 실전 팁 & 주의사항

Tip 1: Private 변수는 진짜 Private이 아님

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class SecretData:
    def __init__(self, secret):
        self.__secret = secret  # "Private" 변수

data = SecretData("비밀")

# ❌ 직접 접근 불가
# print(data.__secret)  # AttributeError

# ⚠️ 하지만 Name Mangling으로 접근 가능 (권장하지 않음!)
print(data._SecretData__secret)  # 비밀

# ✅ 권장: Getter 메서드 제공
class SecretData:
    def __init__(self, secret):
        self.__secret = secret

    @property
    def secret(self):
        return self.__secret

Tip 2: Setter에서 유효성 검사 필수

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
# ❌ 나쁜 예 - 유효성 검사 없음
class Person:
    def __init__(self, age):
        self.age = age  # 음수 입력 가능!

person = Person(-5)  # 문제 발생

# ✅ 좋은 예 - 유효성 검사
class Person:
    def __init__(self, age):
        self.__age = 0
        self.age = age

    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, value):
        if value < 0:
            raise ValueError("나이는 0 이상이어야 합니다.")
        self.__age = value

try:
    person = Person(-5)  # ValueError 발생
except ValueError as e:
    print(f"오류: {e}")

Tip 3: @property는 계산된 속성에 유용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    @property
    def area(self):
        """면적 계산 (읽기 전용)"""
        return self.width * self.height

    @property
    def perimeter(self):
        """둘레 계산 (읽기 전용)"""
        return 2 * (self.width + self.height)

rect = Rectangle(10, 5)
print(rect.area)       # 50 (메서드처럼 () 없이 접근)
print(rect.perimeter)  # 30

# rect.area = 100  # AttributeError (setter 없음)

🧪 연습 문제

문제 1: 시간 클래스

시, 분, 초를 관리하는 Time 클래스를 작성하세요:

요구사항:

  1. __init__(self, hour=0, minute=0, second=0) 생성자
  2. @property로 hour, minute, second Getter/Setter 구현
  3. 유효성 검사 (hour: 0-23, minute/second: 0-59)
  4. total_seconds 속성 (읽기 전용, 총 초 계산)
  5. add_seconds(n) 메서드 (n초 추가, 자동 시간 조정)
1
2
3
4
5
6
7
8
# 사용 예시
time = Time(10, 30, 45)
print(time.hour, time.minute, time.second)  # 10 30 45
print(time.total_seconds)  # 37845

time.minute = 75  # ❌ 유효성 검사 실패
time.add_seconds(100)
print(time.hour, time.minute, time.second)  # 10 32 25
💡 힌트
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
class Time:
    def __init__(self, hour=0, minute=0, second=0):
        self.__hour = 0
        self.__minute = 0
        self.__second = 0
        # setter 호출로 유효성 검사
        self.hour = hour
        self.minute = minute
        self.second = second

    @property
    def hour(self):
        return self.__hour

    @hour.setter
    def hour(self, value):
        if 0 <= value <= 23:
            self.__hour = value
        else:
            print("❌ hour는 0-23 사이여야 합니다.")

    @property
    def total_seconds(self):
        return self.__hour * 3600 + self.__minute * 60 + self.__second

    def add_seconds(self, n):
        total = self.total_seconds + n
        # 시, 분, 초로 변환
        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
72
73
74
75
class Time:
    """시간을 관리하는 클래스"""

    def __init__(self, hour=0, minute=0, second=0):
        """생성자"""
        self.__hour = 0
        self.__minute = 0
        self.__second = 0
        self.hour = hour
        self.minute = minute
        self.second = second

    @property
    def hour(self):
        return self.__hour

    @hour.setter
    def hour(self, value):
        if 0 <= value <= 23:
            self.__hour = value
        else:
            print("❌ hour는 0-23 사이여야 합니다.")

    @property
    def minute(self):
        return self.__minute

    @minute.setter
    def minute(self, value):
        if 0 <= value <= 59:
            self.__minute = value
        else:
            print("❌ minute는 0-59 사이여야 합니다.")

    @property
    def second(self):
        return self.__second

    @second.setter
    def second(self, value):
        if 0 <= value <= 59:
            self.__second = value
        else:
            print("❌ second는 0-59 사이여야 합니다.")

    @property
    def total_seconds(self):
        """총 초 계산 (읽기 전용)"""
        return self.__hour * 3600 + self.__minute * 60 + self.__second

    def add_seconds(self, n):
        """n초 추가"""
        total = self.total_seconds + n

        # 24시간 넘으면 다음 날로
        total = total % (24 * 3600)

        self.__hour = total // 3600
        self.__minute = (total % 3600) // 60
        self.__second = total % 60

    def __str__(self):
        return f"{self.__hour:02d}:{self.__minute:02d}:{self.__second:02d}"

# 테스트
time = Time(10, 30, 45)
print(time)  # 10:30:45
print(f"{time.total_seconds}")

time.minute = 75  # ❌ minute는 0-59 사이여야 합니다.
time.add_seconds(100)
print(time)  # 10:32:25

time.add_seconds(7200)  # 2시간 추가
print(time)  # 12:32:25

문제 2: 학생 성적 클래스

학생 성적을 관리하는 StudentGrade 클래스를 작성하세요:

요구사항:

  1. Private 변수로 이름, 수학, 영어, 과학 점수 저장
  2. @property로 각 과목 점수 Getter/Setter (0-100 범위 체크)
  3. average 속성 (읽기 전용, 평균 계산)
  4. grade 속성 (읽기 전용, 등급 계산: A/B/C/D/F)
1
2
3
4
5
6
7
8
# 등급 기준: A(90~), B(80~), C(70~), D(60~), F(~59)

# 사용 예시
student = StudentGrade("홍길동", 95, 88, 92)
print(student.average)  # 91.67
print(student.grade)    # A

student.math = 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
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
class StudentGrade:
    """학생 성적을 관리하는 클래스"""

    def __init__(self, name, math, english, science):
        """생성자"""
        self.__name = name
        self.__math = 0
        self.__english = 0
        self.__science = 0
        self.math = math
        self.english = english
        self.science = science

    @property
    def name(self):
        return self.__name

    @property
    def math(self):
        return self.__math

    @math.setter
    def math(self, value):
        if 0 <= value <= 100:
            self.__math = value
        else:
            print("❌ 점수는 0-100 사이여야 합니다.")

    @property
    def english(self):
        return self.__english

    @english.setter
    def english(self, value):
        if 0 <= value <= 100:
            self.__english = value
        else:
            print("❌ 점수는 0-100 사이여야 합니다.")

    @property
    def science(self):
        return self.__science

    @science.setter
    def science(self, value):
        if 0 <= value <= 100:
            self.__science = value
        else:
            print("❌ 점수는 0-100 사이여야 합니다.")

    @property
    def average(self):
        """평균 계산 (읽기 전용)"""
        return (self.__math + self.__english + self.__science) / 3

    @property
    def grade(self):
        """등급 계산 (읽기 전용)"""
        avg = self.average
        if avg >= 90:
            return 'A'
        elif avg >= 80:
            return 'B'
        elif avg >= 70:
            return 'C'
        elif avg >= 60:
            return 'D'
        else:
            return 'F'

    def print_report(self):
        """성적표 출력"""
        print(f"\n{'='*40}")
        print(f"이름: {self.name}")
        print(f"{'-'*40}")
        print(f"수학: {self.math}")
        print(f"영어: {self.english}")
        print(f"과학: {self.science}")
        print(f"{'-'*40}")
        print(f"평균: {self.average:.2f}")
        print(f"등급: {self.grade}")
        print(f"{'='*40}\n")

# 테스트
student = StudentGrade("홍길동", 95, 88, 92)
student.print_report()

student.math = 150  # ❌ 점수는 0-100 사이여야 합니다.
student.math = 100
student.print_report()

📝 오늘 배운 내용 정리

개념 설명 예시
Public 변수 누구나 접근 가능 self.name
Protected 변수 _로 시작, 내부용 신호 self._balance
Private 변수 __로 시작, Name Mangling self.__password
@property Getter 메서드를 속성처럼 @property def name(self):
@속성.setter Setter 메서드 @name.setter def name(self, value):
캡슐화 데이터 보호 및 접근 제어 Private + Property

@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
27
class Example:
    def __init__(self, value):
        self.__value = value

    @property
    def value(self):
        """Getter"""
        return self.__value

    @value.setter
    def value(self, new_value):
        """Setter (유효성 검사)"""
        if new_value >= 0:
            self.__value = new_value
        else:
            print("❌ 값은 0 이상이어야 합니다.")

    @property
    def doubled(self):
        """계산된 속성 (읽기 전용)"""
        return self.__value * 2

# 사용
obj = Example(10)
print(obj.value)    # 10 (Getter)
obj.value = 20      # Setter
print(obj.doubled)  # 40 (계산됨)

🔗 관련 자료

📚 이전 학습

Day 32: 생성자와 소멸자 ⭐⭐⭐

어제는 __init__ 메서드로 객체를 초기화하고 __del__ 메서드로 객체를 정리하는 방법을 배웠습니다!

📚 다음 학습

Day 34: 클래스 메서드와 정적 메서드 ⭐⭐⭐

내일은 클래스 메서드와 정적 메서드를 배우고 다양한 메서드 활용법을 익힙니다!


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

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