클래스와 인스턴스
학습 목표
class로 클래스를 정의하고 인스턴스를 생성할 수 있다
__init__과 self의 역할을 이해한다
- 인스턴스 변수와 클래스 변수의 차이를 구분할 수 있다
- 인스턴스 메서드, 클래스 메서드, 정적 메서드를 구분하여 사용할 수 있다
왜 중요한가
클래스는 관련된 데이터와 기능을 하나의 단위로 묶는 설계 도구입니다. ML/DL에서 모든 모델(nn.Module), 데이터셋(Dataset), 옵티마이저(Optimizer)가 클래스로 정의됩니다. 클래스를 이해해야 프레임워크 코드를 읽고 확장할 수 있습니다.
클래스 정의와 인스턴스 생성
# 클래스 정의
class Dog:
"""개를 나타내는 클래스"""
def __init__(self, name, breed):
"""인스턴스 초기화 메서드"""
self.name = name # 인스턴스 변수
self.breed = breed # 인스턴스 변수
def bark(self):
"""짖는 메서드"""
print(f"{self.name}: 멍멍!")
# 인스턴스 생성
my_dog = Dog("바둑이", "진돗개")
your_dog = Dog("맥스", "리트리버")
# 메서드 호출
my_dog.bark() # 바둑이: 멍멍!
your_dog.bark() # 맥스: 멍멍!
# 속성 접근
print(my_dog.name) # "바둑이"
print(my_dog.breed) # "진돗개"
__init__과 self
class Student:
def __init__(self, name, score):
# self: 생성될 인스턴스 자신을 가리킴
self.name = name # self.name = 인스턴스 변수
self.score = score
self.grade = self._calculate_grade() # 다른 메서드 호출
def _calculate_grade(self):
if self.score >= 90:
return "A"
elif self.score >= 80:
return "B"
return "C"
def info(self):
return f"{self.name}: {self.score}점 ({self.grade})"
s = Student("김철수", 85)
print(s.info()) # 김철수: 85점 (B)
__init__은 생성자(constructor)가 아닌 초기화(initializer) 메서드입니다. 실제 생성자는 __new__이며, __init__은 이미 생성된 인스턴스를 초기화합니다.
인스턴스 변수 vs 클래스 변수
class Counter:
total_count = 0 # 클래스 변수: 모든 인스턴스가 공유
def __init__(self, name):
self.name = name # 인스턴스 변수: 각 인스턴스 고유
self.count = 0 # 인스턴스 변수
Counter.total_count += 1
def increment(self):
self.count += 1
a = Counter("A")
b = Counter("B")
a.increment()
a.increment()
b.increment()
print(a.count) # 2 (a 고유)
print(b.count) # 1 (b 고유)
print(Counter.total_count) # 2 (클래스 공유)
클래스 변수가 가변 객체(리스트, 딕셔너리)인 경우 주의하세요.class Student:
grades = [] # 모든 인스턴스가 공유!
def add_grade(self, grade):
self.grades.append(grade)
s1 = Student()
s2 = Student()
s1.add_grade("A")
print(s2.grades) # ["A"] <- s2에도 영향!
메서드 종류
인스턴스 메서드
class Calculator:
def __init__(self):
self.result = 0
def add(self, value): # 인스턴스 메서드: self가 첫 인자
self.result += value
return self # 메서드 체이닝 가능
calc = Calculator()
calc.add(5).add(3).add(2)
print(calc.result) # 10
클래스 메서드 (@classmethod)
class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day
@classmethod
def from_string(cls, date_string):
"""문자열로부터 Date 인스턴스를 생성합니다."""
year, month, day = map(int, date_string.split("-"))
return cls(year, month, day) # cls = Date 클래스
@classmethod
def today(cls):
"""오늘 날짜의 Date 인스턴스를 생성합니다."""
import datetime
t = datetime.date.today()
return cls(t.year, t.month, t.day)
def __str__(self):
return f"{self.year}-{self.month:02d}-{self.day:02d}"
d1 = Date(2024, 1, 15)
d2 = Date.from_string("2024-06-30") # 팩토리 메서드
d3 = Date.today()
정적 메서드 (@staticmethod)
class MathUtils:
@staticmethod
def is_even(n):
"""self도 cls도 받지 않는 순수 함수"""
return n % 2 == 0
@staticmethod
def celsius_to_fahrenheit(celsius):
return celsius * 9/5 + 32
print(MathUtils.is_even(4)) # True
print(MathUtils.celsius_to_fahrenheit(30)) # 86.0
비교 요약
| 메서드 | 데코레이터 | 첫 인자 | 접근 가능 |
|---|
| 인스턴스 | 없음 | self | 인스턴스 + 클래스 변수 |
| 클래스 | @classmethod | cls | 클래스 변수만 |
| 정적 | @staticmethod | 없음 | 외부 데이터만 |
접근 제어 관례
Python에는 접근 제어 키워드(private, public)가 없습니다. 대신 이름 규칙을 사용합니다.
class Account:
def __init__(self, owner, balance):
self.owner = owner # public
self._balance = balance # protected (관례)
self.__pin = "1234" # private (네임 맹글링)
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError("잔액은 음수일 수 없습니다")
self._balance = value
acc = Account("김철수", 10000)
print(acc.balance) # 10000 (property 접근)
acc.balance = 5000 # setter 호출
# acc.balance = -100 # ValueError!
AI/ML에서의 활용
# PyTorch 모델 패턴
class SimpleModel:
"""간단한 모델 클래스 (PyTorch nn.Module 패턴 모사)"""
def __init__(self, input_size, hidden_size, output_size):
self.input_size = input_size
self.hidden_size = hidden_size
self.output_size = output_size
self._is_trained = False
def forward(self, x):
"""순전파 계산"""
# 실제로는 행렬 연산
return x
def train(self, data, epochs=10):
"""모델을 학습합니다."""
for epoch in range(epochs):
loss = self._train_step(data)
print(f"Epoch {epoch+1}: loss={loss:.4f}")
self._is_trained = True
def _train_step(self, data):
"""단일 학습 스텝 (내부 메서드)"""
return 1.0 / (len(data) + 1)
@classmethod
def from_config(cls, config):
"""설정 딕셔너리로부터 모델 생성"""
return cls(
input_size=config["input_size"],
hidden_size=config["hidden_size"],
output_size=config["output_size"]
)
config = {"input_size": 784, "hidden_size": 128, "output_size": 10}
model = SimpleModel.from_config(config)
Python의 설계 철학인 “명시적이 암시적보다 낫다(Explicit is better than implicit)“에 따른 것입니다. self를 명시함으로써 인스턴스 변수와 지역 변수를 명확히 구분합니다.
__init__에서 모든 속성을 정의해야 하나요?
권장됩니다. __init__에서 모든 인스턴스 속성을 초기화하면 인스턴스의 구조가 명확해지고, IDE의 자동 완성과 타입 체커가 정확히 동작합니다.
체크리스트
다음 문서