Python 클래스 vs 인스턴스 상태
C++ 배경에서 Python을 볼 때 모호하게 느껴지는 이유: Python은 선언부 없이 런타임에 변수가 생겨서 클래스/인스턴스 구분이 눈에 잘 안 띈다.
C++와 1:1 대응
class Counter {
public:
static int total; // 클래스 상태 — 모든 인스턴스 공유
int count; // 인스턴스 상태 — 각 객체마다 독립
};
class Counter:
total = 0 # 클래스 변수 ← C++ static int total
def __init__(self):
self.count = 0 # 인스턴스 변수 ← C++ int count
구조는 동일하다. 차이는 Python이 __init__ 안에서 self.count = 0을 처음 실행하는 순간 인스턴스 변수가 생긴다는 것 — C++처럼 클래스 본문에 선언부가 없다.
메모리 관점 비교
C++에서는 메모리 위치로 구분이 명확하다:
static멤버 → BSS/data 영역 (프로그램 시작 시 할당)- 인스턴스 멤버 → 스택(값 타입) 또는 힙(
new)
Python(CPython)에서는 모든 객체가 힙에 있다. 클래스 변수도 힙, 인스턴스 변수도 힙. 스택에는 참조(포인터)만 올라간다. 따라서 메모리 위치보다 생명주기와 공유 범위로 이해하는 것이 더 정확하다:
| | C++ 위치 | Python 위치 | 공유 범위 | |---|---|---|---| | 클래스 변수 | BSS/data 영역 | 힙 (클래스 객체 내부) | 모든 인스턴스 공유 | | 인스턴스 변수 | 스택 or 힙 | 힙 (인스턴스 객체 내부) | 해당 인스턴스만 |
메서드 데코레이터 선택 기준
| 데코레이터 | 첫 인자 | 언제 쓰나 |
|---|---|---|
| 없음 | self | 인스턴스 상태(self.x)가 필요할 때 |
| @classmethod | cls | 클래스 자체를 다형적으로 다뤄야 할 때 (주로 팩토리 메서드) |
| @staticmethod | 없음 | self도 cls도 불필요한 유틸리티 |
주의: @classmethod는 단순히 "클래스 변수를 접근할 때"가 아니다. 클래스 변수는 @staticmethod에서도 Counter.total로 이름 직접 접근이 가능하다. @classmethod가 필요한 핵심은 서브클래스 상속 시 cls가 서브클래스를 가리켜야 할 때 — 팩토리 패턴이 대표적.
class Animal:
@classmethod
def create(cls):
return cls() # Dog.create() 하면 Dog 인스턴스가 만들어짐
class Dog(Animal):
pass
Dog.create() # cls = Dog → Dog() 반환