Skip to main content

클래스와 인스턴스

학습 목표

  • 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인스턴스 + 클래스 변수
클래스@classmethodcls클래스 변수만
정적@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__에서 모든 인스턴스 속성을 초기화하면 인스턴스의 구조가 명확해지고, IDE의 자동 완성과 타입 체커가 정확히 동작합니다.

체크리스트

  • class로 클래스를 정의하고 인스턴스를 생성할 수 있다
  • __init__self의 역할을 설명할 수 있다
  • 인스턴스 변수와 클래스 변수의 차이를 구분할 수 있다
  • 인스턴스/클래스/정적 메서드를 구분하여 사용할 수 있다
  • @property로 속성 접근을 제어할 수 있다

다음 문서