6장: 클래스 — 객체를 설계하는 틀¶
함수로는 부족할 때¶
함수는 코드를 재사용할 수 있게 해줍니다.
그런데 데이터와 기능을 하나로 묶어서 관리하고 싶을 때는 어떻게 할까요?
# 학생 2명 — 이름, 점수, 합격 여부를 각각 관리
name1 = "Alice"; score1 = 88; passed1 = score1 >= 60
name2 = "Bob"; score2 = 45; passed2 = score2 >= 60
학생이 100명이면? 변수가 300개 필요합니다.
클래스를 쓰면 Student 하나로 묶어서 관리할 수 있습니다.
주요 용어 정리¶
| 용어 | 의미 | 예시 |
|---|---|---|
| 클래스(Class) | 제품의 설계도 | class Monster: |
| 객체(Object) | 설계도로 만들어진 실제 제품 | m = Monster("슬라임") |
| 속성(Attribute) | 클래스 안에 있는 변수 | self.name, self.hp |
| 메서드(Method) | 클래스 안에 있는 함수 | def attack(self): |
| 생성자(Constructor) | 객체가 생성될 때 가장 먼저 실행되는 함수 | def __init__(self): |
몬스터 클래스로 이해하기¶
class Monster: # 설계도
def __init__(self, name, hp): # 생성자
self.name = name # 속성 (attribute)
self.hp = hp # 속성 (attribute)
def attack(self): # 메서드 (method)
print(f"{self.name} 공격! (HP: {self.hp})")
def is_alive(self): # 메서드
return self.hp > 0
# 설계도(Monster)로 실제 객체 생성
slime = Monster("슬라임", 30) # 객체 1
goblin = Monster("고블린", 50) # 객체 2
slime.attack() # 슬라임 공격! (HP: 30)
goblin.attack() # 고블린 공격! (HP: 50)
print(slime.is_alive()) # True
각 객체(slime, goblin)는 같은 설계도(Monster)로 만들었지만 속성값은 독립적입니다.
객체(Object)란?¶
객체 = 속성(데이터) + 메서드(기능)
강아지 객체:
속성: 이름 = "초코", 품종 = "말티즈", 나이 = 3
메서드: 짖는다(), 먹는다(), 잔다()
현실 세계의 모든 사물을 속성과 행동으로 모델링할 수 있습니다.
클래스(Class) 정의¶
클래스 = 객체를 찍어내는 설계도
class Dog:
def __init__(self, name, breed):
self.name = name # 인스턴스 변수
self.breed = breed
def bark(self):
print(f"{self.name}: 왈왈!")
# 클래스로 객체(인스턴스) 생성
d1 = Dog("초코", "말티즈")
d2 = Dog("보리", "시바견")
d1.bark() # 초코: 왈왈!
d2.bark() # 보리: 왈왈!
__init__ — 생성자¶
객체가 만들어질 때 자동으로 호출되는 메서드입니다.
초기 속성(인스턴스 변수)을 여기서 설정합니다.
class Circle:
def __init__(self, radius):
self.radius = radius # self.radius : 인스턴스 변수
def area(self):
import math
return math.pi * self.radius ** 2
c = Circle(5)
print(c.radius) # 5
print(round(c.area(), 2)) # 78.54
self — 나 자신을 가리키는 참조¶
모든 인스턴스 메서드의 첫 번째 매개변수는 self입니다.
self는 현재 객체를 가리킵니다.
class Counter:
def __init__(self):
self.count = 0 # 이 객체의 count
def increment(self):
self.count += 1 # 이 객체의 count를 증가
def get(self):
return self.count
c1 = Counter()
c2 = Counter()
c1.increment()
c1.increment()
print(c1.get()) # 2 (c1만 증가)
print(c2.get()) # 0 (c2는 그대로)
self.count는 각 객체마다 독립적인 공간에 저장됩니다.
인스턴스 변수 vs 클래스 변수¶
| 인스턴스 변수 | 클래스 변수 | |
|---|---|---|
| 정의 위치 | __init__ 안, self.이름 |
클래스 블록, self 없이 |
| 공유 여부 | 객체마다 독립 | 모든 객체가 공유 |
| 접근 | self.이름 |
클래스명.이름 또는 self.이름 |
class Student:
school = "한국고등학교" # 클래스 변수 — 모두 공유
def __init__(self, name, score):
self.name = name # 인스턴스 변수
self.score = score
s1 = Student("Alice", 90)
s2 = Student("Bob", 75)
print(s1.school) # 한국고등학교
print(s2.school) # 한국고등학교
print(Student.school) # 한국고등학교
Student.school = "서울고등학교" # 클래스 변수 변경 → 전체 반영
print(s1.school) # 서울고등학교
print(s2.school) # 서울고등학교
클래스 변수의 함정 ⚠️¶
class Dog:
tricks = [] # 클래스 변수 (리스트는 공유됨!)
def add_trick(self, trick):
self.tricks.append(trick) # 공유 리스트에 추가됨
d1 = Dog()
d2 = Dog()
d1.add_trick("앉아")
d2.add_trick("손")
print(d1.tricks) # ['앉아', '손'] ← d1만 추가했는데 d2 것도 있다!
print(d2.tricks) # ['앉아', '손']
해결책: 리스트는 __init__ 안에서 인스턴스 변수로 만들기
class Dog:
def __init__(self):
self.tricks = [] # 각 객체마다 독립적인 리스트
def add_trick(self, trick):
self.tricks.append(trick)
d1 = Dog(); d2 = Dog()
d1.add_trick("앉아")
d2.add_trick("손")
print(d1.tricks) # ['앉아']
print(d2.tricks) # ['손']
특수 메서드(Magic Method)¶
__이름__ 형태의 메서드로, 파이썬이 특정 상황에서 자동 호출합니다.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self): # print() 할 때 호출
return f"({self.x}, {self.y})"
def __add__(self, other): # + 연산자 사용 시 호출
return Point(self.x + other.x, self.y + other.y)
p1 = Point(1, 2)
p2 = Point(3, 4)
print(p1) # (1, 2) ← __str__ 호출
print(p1 + p2) # (4, 6) ← __add__ 호출
상속(Inheritance)¶
기존 클래스의 속성과 메서드를 물려받아 새 클래스를 만듭니다.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
print(f"{self.name}: ...")
class Cat(Animal): # Animal을 상속
def speak(self): # 메서드 오버라이딩
print(f"{self.name}: 야옹")
class Dog(Animal):
def speak(self):
print(f"{self.name}: 멍멍")
animals = [Cat("나비"), Dog("초코"), Animal("?")]
for a in animals:
a.speak()
나비: 야옹
초코: 멍멍
?: ...
super() — 부모 메서드 호출¶
class Animal:
def __init__(self, name):
self.name = name
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name) # 부모의 __init__ 호출
self.breed = breed # 자식만의 추가 속성
def info(self):
return f"{self.name} ({self.breed})"
d = Dog("초코", "말티즈")
print(d.info()) # 초코 (말티즈)
super().__init__() 을 빠뜨리면 부모의 속성이 초기화되지 않습니다.
접근 제어 — _와 __¶
| 표기 | 의미 | 실제 동작 |
|---|---|---|
name |
공개 (public) | 어디서든 접근 |
_name |
비공개 관례 | 접근 가능하지만 "건드리지 마세요" |
__name |
이름 맹글링 | _클래스명__name 으로 변환됨 |
class BankAccount:
def __init__(self, balance):
self.__balance = balance # 외부 직접 접근 차단
def deposit(self, amount):
if amount > 0:
self.__balance += amount
def get_balance(self):
return self.__balance
acc = BankAccount(1000)
# print(acc.__balance) # AttributeError!
acc.deposit(500)
print(acc.get_balance()) # 1500
실습 미션¶
미션 1: 직사각형 클래스¶
class Rectangle:
def __init__(self, width, height):
# 여기에 코드 작성
pass
def area(self):
# 넓이 반환
pass
def perimeter(self):
# 둘레 반환
pass
def __str__(self):
# "Rectangle(3x5)" 형태로 반환
pass
r = Rectangle(3, 5)
print(r) # Rectangle(3x5)
print(r.area()) # 15
print(r.perimeter()) # 16
미션 2: 은행 계좌¶
class BankAccount:
def __init__(self, owner, balance=0):
pass
def deposit(self, amount): # 입금 (0 이하 무시)
pass
def withdraw(self, amount): # 출금 (잔액 초과 시 "잔액 부족")
pass
def __str__(self):
pass
acc = BankAccount("Alice", 1000)
acc.deposit(500)
acc.withdraw(200)
print(acc) # Alice: 1300원
acc.withdraw(2000) # 잔액 부족
미션 3: 동물 상속¶
class Animal:
def __init__(self, name, age):
self.name = name
self.age = age
def info(self):
return f"{self.name} ({self.age}살)"
class Dog(Animal):
def __init__(self, name, age, breed):
# super() 사용
pass
def speak(self):
return f"{self.name}: 멍멍!"
class Cat(Animal):
def speak(self):
return f"{self.name}: 야옹~"
d = Dog("초코", 3, "말티즈")
c = Cat("나비", 5)
print(d.info()) # 초코 (3살)
print(d.speak()) # 초코: 멍멍!
print(c.speak()) # 나비: 야옹~
미션 4 (심화): 성적 관리 시스템¶
class Student:
student_count = 0 # 클래스 변수: 전체 학생 수
def __init__(self, name):
self.name = name
self.scores = {}
Student.student_count += 1
def add_score(self, subject, score):
self.scores[subject] = score
def average(self):
if not self.scores:
return 0
return sum(self.scores.values()) / len(self.scores)
def __str__(self):
return f"{self.name}: 평균 {self.average():.1f}"
s1 = Student("Alice")
s1.add_score("수학", 90)
s1.add_score("영어", 85)
s2 = Student("Bob")
s2.add_score("수학", 70)
print(s1) # Alice: 평균 87.5
print(Student.student_count) # 2
핵심 요약¶
| 개념 | 설명 |
|---|---|
class |
객체를 만드는 설계도 |
__init__ |
객체 생성 시 자동 호출되는 생성자 |
self |
현재 객체 자신을 가리키는 참조 |
| 인스턴스 변수 | self.이름 — 객체마다 독립적 |
| 클래스 변수 | 클래스 블록에 정의 — 모든 객체가 공유 |
| 클래스 변수 함정 | 가변 객체(리스트 등)는 __init__에 |
__str__ |
print() 시 호출되는 문자열 표현 |
| 상속 | class Child(Parent): — 부모 기능 물려받기 |
super() |
부모 클래스의 메서드 호출 |
__name |
이름 맹글링 — 외부 직접 접근 차단 |