파이썬72번5 MIN
인스턴스 속성 vs 클래스 속성
Python에서 인스턴스 속성과 클래스 속성의 차이, 속성 조회 순서, 가변 클래스 속성 함정, 클래스 속성의 올바른 활용 방법을 설명합니다.
이 글은 PALDYN Tech Blog의 글입니다.원문 보기
지난 글에서 클래스의 기본 구조를 살펴보았습니다. 클래스 안에서 속성을 정의하는 위치에 따라 인스턴스 속성과 클래스 속성으로 나뉩니다. 이 두 가지를 혼동하면 예상치 못한 버그가 생기므로, 차이를 명확히 이해하는 것이 중요합니다.
인스턴스 속성
__init__ 안에서 self.변수명 = 값 형태로 선언한 속성입니다. 각 인스턴스의 __dict__에 저장되며, 인스턴스마다 독립된 값을 가집니다.
class Dog:
def __init__(self, name, age):
self.name = name # 인스턴스 속성
self.age = age
d1 = Dog("Rex", 3)
d2 = Dog("Max", 5)
print(d1.name) # Rex
print(d2.name) # Max
print(d1.__dict__) # {'name': 'Rex', 'age': 3}
d1과 d2는 서로 다른 딕셔너리를 가지므로 값이 완전히 독립됩니다.
클래스 속성
클래스 본문에서, __init__ 바깥에 직접 선언한 속성입니다. 클래스의 __dict__에 한 번만 저장되며 모든 인스턴스가 공유합니다.
class Dog:
species = "Canis lupus" # 클래스 속성
count = 0
def __init__(self, name):
self.name = name
Dog.count += 1 # 클래스 속성 직접 접근
d1 = Dog("Rex")
d2 = Dog("Max")
print(Dog.species) # Canis lupus
print(Dog.count) # 2
print(d1.species) # Canis lupus (클래스 속성 조회)
클래스 속성은 상수, 공유 카운터, 기본값 등에 유용합니다.
속성 조회 순서
obj.attr을 평가할 때 Python은 다음 순서로 탐색합니다.
obj.__dict__(인스턴스 딕셔너리)type(obj).__dict__(클래스 딕셔너리)- 상위 클래스 MRO 순서대로 탐색
- 없으면
AttributeError
class Dog:
species = "Canis lupus"
def __init__(self, name):
self.name = name
d = Dog("Rex")
print(d.species) # Canis lupus ← 클래스 속성에서 찾음
d.species = "wolf" # 인스턴스 딕셔너리에 추가 (클래스 속성 가림)
print(d.species) # wolf
print(Dog.species) # Canis lupus ← 클래스 속성은 변경 안 됨
인스턴스에서 클래스 속성과 같은 이름에 값을 대입하면, 클래스 속성을 수정하는 것이 아니라 인스턴스 딕셔너리에 새 항목이 생겨 클래스 속성을 가립니다(shadow).
가변 클래스 속성 함정
클래스 속성에 리스트나 딕셔너리 같은 가변(mutable) 객체를 두면 예상치 못한 공유가 발생합니다.
class Kennel:
dogs = [] # 위험: 클래스 속성
def add(self, dog):
self.dogs.append(dog) # 클래스 속성 직접 변경
k1 = Kennel()
k2 = Kennel()
k1.add("Rex")
print(k2.dogs) # ['Rex'] — k2에 추가하지 않았는데!
self.dogs.append는 재할당이 아닌 in-place 변경이라, 인스턴스 딕셔너리에 새 항목을 만들지 않습니다. 그 결과 k1.dogs와 k2.dogs가 같은 리스트 객체를 가리키게 됩니다. 해결책은 __init__에서 인스턴스 속성으로 초기화하는 것입니다.
class Kennel:
def __init__(self):
self.dogs = [] # 인스턴스마다 독립된 리스트
def add(self, dog):
self.dogs.append(dog)
k1 = Kennel()
k2 = Kennel()
k1.add("Rex")
print(k2.dogs) # [] — 정상
클래스 속성의 올바른 활용
클래스 속성은 다음과 같은 목적에 적합합니다.
class Config:
DEBUG = False # 전체 공유 상수
VERSION = "1.0.0"
class Counter:
total = 0
def __init__(self):
Counter.total += 1 # 인스턴스 생성 횟수 추적
class Animal:
sound = "" # 서브클래스에서 오버라이드할 기본값
def speak(self):
return self.sound # 인스턴스에 없으면 클래스 속성 반환
서브클래스에서 클래스 속성을 오버라이드하면 해당 클래스와 그 인스턴스만 새 값을 가집니다.
class Dog(Animal):
sound = "Woof"
class Cat(Animal):
sound = "Meow"
print(Dog().speak()) # Woof
print(Cat().speak()) # Meow
속성 존재 여부 확인
d = Dog("Rex")
print(hasattr(d, "name")) # True
print(hasattr(d, "unknown")) # False
# 기본값 지정 접근
age = getattr(d, "age", 0) # 없으면 0 반환
# 속성 동적 설정·삭제
setattr(d, "color", "brown")
delattr(d, "color")
vars(obj)는 obj.__dict__와 동일하게 인스턴스 속성 딕셔너리를 반환합니다.
지난 글: 클래스 기초: 사용자 정의 타입 만들기
다음 글: self 키워드 완전 이해
읽어주셔서 감사합니다. 😊

