C.W.K.
Stream
Lesson 02 of 07 · published

인스턴스 vs 클래스 속성 — mutable 디폴트 함정 또

~18 min · instance-attribute, class-attribute, mutable-default

Level 0호기심
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

두 종류의 속성

메서드 안에서 (보통 __init__) self.x = ... 로 정의된 속성은 *인스턴스* 속성 — 각 인스턴스가 자기 거. 클래스 레벨 (메서드 안 X, 클래스 본문) 에 정의된 속성은 *클래스* 속성 — 모든 인스턴스 공유. 둘 다 instance.attribute 로 접근 — 그래서 구분이 미묘.

Lookup 순서 — 인스턴스 먼저, 그 다음 클래스

obj.x 접근하면 Python 은 인스턴스의 __dict__ 먼저 봐. 없으면 클래스 체크. 없으면 상속 체인 위로. 그래서 obj.x = value 가 클래스 속성을 *가리는* 인스턴스 속성 만들어 — 클래스 자체 안 바꿔.

Mutable 클래스 속성 함정

mutable 객체를 클래스 속성으로 (클래스 레벨에 history = []) 두면 모든 인스턴스 공유. instance.history.append(x) 가 가리지 *않고* — 공유 list 변경. mutable 디폴트 인자와 같은 함정. 해결책 — __init__ 안에서 할당 — self.history = [].

주의: 클래스 속성은 진짜 상수와 클래스 전반 설정 (RETRY_LIMIT = 3) 에 좋고, mutable 한 거엔 나빠. 룰 — 원시 + immutable 이면 클래스 레벨 OK. list / dict / set 이면 인스턴스에.

클래스 레벨 vs 인스턴스 레벨 메서드 — 차이는 __get__

클래스 본문에 정의된 함수가 메서드 되는 이유 — Python 의 descriptor 프로토콜이 접근 시 bound 메서드로 변환. 그래서 self 가 자동으로 나타나. 함수 아닌 클래스 속성은 이 처리 없어 — 자기 자신으로 돌아옴.

Code

인스턴스 vs 클래스 속성 lookup·python
class Robot:
    species = "robot"            # 클래스 속성 — 공유

    def __init__(self, name):
        self.name = name          # 인스턴스 속성 — 각자

r1 = Robot("R1")
r2 = Robot("R2")

print(r1.species, r2.species)    # robot robot   — 같은 값
print(r1.name, r2.name)          # R1 R2          — 다름

# 인스턴스에 설정하면 가림 — 클래스 안 바꿈
r1.species = "android"
print(r1.species, r2.species)    # android robot
print(Robot.species)             # robot          — 클래스 그대로
Mutable 클래스 속성 — 함정·python
# 안 좋음
class Bad:
    history = []                  # 클래스 속성 — 인스턴스 간 공유

    def __init__(self, name):
        self.name = name

    def log(self, event):
        self.history.append(event)

a = Bad("a")
b = Bad("b")
a.log("x")
b.log("y")
print(a.history)        # ['x', 'y']  <- a 가 b 의 log 봄!
print(b.history)        # ['x', 'y']
print(a.history is b.history)   # True — 같은 list

# 좋음 — __init__ 안에서 인스턴스에 할당
class Good:
    def __init__(self, name):
        self.name = name
        self.history = []         # 인스턴스 속성 — 매번 새 list

    def log(self, event):
        self.history.append(event)

a = Good("a")
b = Good("b")
a.log("x")
b.log("y")
print(a.history)        # ['x']
print(b.history)        # ['y']
클래스 레벨 상수 — OK 그리고 idiomatic·python
class Connection:
    DEFAULT_TIMEOUT = 30          # 클래스 속성 — 공유 상수
    MAX_RETRIES = 3

    def __init__(self, host):
        self.host = host

    def connect(self, timeout=None):
        if timeout is None:
            timeout = self.DEFAULT_TIMEOUT     # 인스턴스 통해 접근
        return f"connecting to {self.host} (timeout={timeout})"

c = Connection("localhost")
print(c.connect())               # 클래스의 30 사용
print(c.connect(timeout=10))     # 명시적 override
print(Connection.MAX_RETRIES)    # 클래스로 직접 접근
dict 들여다보기 — 인스턴스 vs 클래스·python
class Robot:
    species = "robot"

    def __init__(self, name):
        self.name = name

r = Robot("R1")

print(r.__dict__)              # {'name': 'R1'}            — 인스턴스 속성만
print("species" in r.__dict__) # False                     — 인스턴스에 X
print("species" in Robot.__dict__)  # True                  — 클래스에 있음

# r.species 작동하는 이유 — Python 이 클래스로 fallback
print(r.species)               # 'robot'

External links

Exercise

클래스 Quiz — 클래스 레벨 상수 MAX_QUESTIONS = 10, 인스턴스 속성 answers (list, __init__ 에서 새로 init), 메서드 add_answer(text) (append + len 이 MAX 넘으면 RuntimeError). Quiz 두 인스턴스 만들어서 각자 자기 answers — 한쪽 추가가 다른 쪽 영향 없는지 확인. 그 다음 일부러 함정 — 클래스 레벨에 answers = [] 로 다시 정의, 같은 테스트 반복, 버그 관찰.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.