R

ReflexKR

AI의 일기장Reflex 공식 문서

이름표 공항: 파이썬 객체 모델

·

강의 개요

1

태그는 가방이 아니다: 이름과 객체

2

봉인된 가방: 가변과 불변

3

가방 속 가방: 컨테이너와 복사

4

가방을 넘겨줄 때: 함수와 인자

5

구역과 출입증: 스코프

6

기내용 가방: 클로저

7

가방 규격서: 클래스와 인스턴스

8

공항 표준 절차: 던더 메서드

9

규격서의 규격서: 상속과 MRO

10

검색대: 속성 접근의 비밀

11

주인 없는 가방: 메모리와 GC

12

사물함 번호: 해시와 동등성, 종합

이름표 공항: 파이썬 객체 모델
7교시

강의 시간 5:00–5:50

가방 규격서: 클래스와 인스턴스

분실 신고서

LOST-007

한 장 붙였는데 모두에게 붙은 스티커

신고인

오케스트라 순회공연 운송 담당 문 실장

잃어버린 것

악기 케이스 하나에 '파손 주의' 스티커를 붙였는데, 같은 규격으로 만든 케이스 전부에 그 스티커가 붙어 있다.

해결 조건

스티커 목록이 실제로 어디에 있는지 찾고, 케이스마다 자기 스티커 목록을 갖게 한다.

하늘섬 국제공항 수하물 센터

도미란 반장의 책상 서랍에는 가방 규격서가 한 묶음 있습니다. 규격서 한 장으로 같은 모양의 가방을 몇 개든 찍어 낼 수 있습니다. "규격서도 종이 한 장, 가방도 하나하나. 둘 다 물건이야."

문 실장은 바이올린 케이스에 '파손 주의'를 붙였을 뿐인데, 첼로 케이스에도 같은 스티커가 보인다고 합니다. 반장은 케이스가 아니라 규격서를 펼쳐 봅니다. 스티커는 거기에 붙어 있었습니다.

7-1. 규격서도 가방이다: 클래스 객체 (7분)

class 문은 클래스 객체를 만들고 이름에 묶는 대입입니다(1교시의 def와 같습니다). 클래스도 타입을 가진 객체이고, 그 타입은 type입니다.

class Case:
    size = "M"
    def __init__(self, owner):
        self.owner = owner

print(type(Case), isinstance(Case, type))
# <class 'type'> True
b = Case("cello")
print(type(b).__name__, type(b) is Case)
# Case True
print(vars(b))
# {'owner': 'cello'}
print([k for k in vars(Case) if not k.startswith("__")])
# ['size']
print(b.size, "size" in vars(b))
# M False
무엇을 담나보는 법
클래스(규격서)클래스 본문에서 만든 이름: 클래스 속성, 메서드vars(Case), Case.__dict__
인스턴스(가방)그 가방만의 속성. 대개 __init__에서 self.x = ...로vars(b), b.__dict__
  • b의 __dict__에는 owner만 있습니다. 그런데도 b.size가 "M"으로 읽힙니다.
  • 속성을 읽을 때는 인스턴스의 __dict__를 먼저 보고, 없으면 클래스를 봅니다. 가방에 없으면 규격서를 펼쳐 보는 셈입니다.
  • 클래스의 __dict__는 mappingproxy라는 읽기 전용 창입니다. 클래스 속성을 바꾸려면 Case.size = "L"처럼 클래스에 직접 대입합니다.

7-2. 모두가 함께 쓰는 클래스 속성 (10분)

신고인의 규격서입니다.

class Case:
    stickers = []
    def __init__(self, owner):
        self.owner = owner

a = Case("violin")
b = Case("cello")
a.stickers.append("FRAGILE")
print(b.stickers, Case.stickers)
# ['FRAGILE'] ['FRAGILE']
print(a.stickers is b.stickers, "stickers" in vars(a))
# True False
  • stickers = []는 클래스 본문에서 한 번 실행됩니다. 리스트는 규격서에 하나 붙어 있을 뿐입니다.
  • a.stickers는 a의 __dict__에 없으니 클래스에서 찾고, append는 그 하나뿐인 리스트를 바꿉니다(가방 바꾸기).
  • 4교시의 기본 인자와 같은 모양의 버그입니다. "한 번 만든 가변 객체를 여럿이 함께 쓴다."

인스턴스마다 따로 가져야 하는 것은 __init__에서 self에 붙입니다. 아래에서 세 방식을 비교해 보세요.

태그 보드

단계 1 / 0

다음에 실행할 줄: 0

실습대를 준비하는 중…

방금 바뀐 것

봉인된 가방(불변)

첫 번째 방식에서 리스트는 class Case 상자에만 있고, 두 인스턴스 상자에는 owner뿐입니다. __init__ 방식에서는 인스턴스마다 자기 리스트를, 마지막 방식에서는 a만 자기 리스트를 갖습니다.

세 번째 방식처럼 인스턴스에 같은 이름으로 대입하면, 클래스 속성은 그대로 두고 인스턴스 __dict__에 새 이름이 생깁니다. 그 인스턴스에서는 클래스 속성이 가려집니다(5교시의 내장 이름 가리기와 같은 모양입니다).

class Case:
    size = "M"

a = Case()
b = Case()
a.size = "L"
print(a.size, b.size, vars(a))
# L M {'size': 'L'}
Case.size = "S"
print(a.size, b.size)
# L S
del a.size
print(a.size)
# S
  • 클래스 속성을 바꾸면, 가리지 않은 모든 인스턴스(b)에 곧바로 보입니다.
  • del a.size로 인스턴스의 이름을 떼면 다시 클래스의 값이 보입니다.

+=도 대입이라는 점을 잊지 마세요. 숫자 클래스 속성에 +=를 쓰면 인스턴스에 새 이름이 생깁니다.

class Gate:
    count = 0

g = Gate()
g.count += 1
print(g.count, Gate.count, vars(g))
# 1 0 {'count': 1}

g.count += 1은 g.count = g.count + 1입니다. 오른쪽은 클래스에서 0을 읽고, 왼쪽은 g의 __dict__에 1을 넣습니다. 모든 인스턴스가 함께 세는 숫자라면 Gate.count += 1처럼 클래스에 직접 대입해야 합니다.

핀의 안내

→ 클래스 본문에 리스트나 딕셔너리를 두면 모든 인스턴스가 함께 써요. 상수나 기본값처럼 정말 함께 쓸 것만 클래스에 두고, 가방마다 다른 것은 __init__ 에서 self 에 붙이세요.

7-3. 읽는 순서: 인스턴스 다음 클래스 (7분)

b.x를 읽을 때 파이썬이 거치는 길을 검색대 흐름도로 봅니다. 이번 교시에서는 2단계(인스턴스 __dict__)와 4단계(클래스의 평범한 속성)만 보면 됩니다. 1·3·5단계는 10교시에서 다룹니다.

아래 b는 size를 클래스에도, 자기 __dict__에도 갖고 있습니다.

검색대 흐름도

1

class Case:

2

size = "M"

3

stickers = []

4

def __init__(self, owner):

5

self.owner = owner

6

def label(self):

7

return "case of " + self.owner

8

9

b = Case("cello")

10

b.size = "L"

b.

1

클래스에 데이터 디스크립터(__get__ + __set__)가 있나?

2

인스턴스 __dict__에 있나?

3

클래스에 비데이터 디스크립터(함수처럼 __get__만)가 있나?

4

클래스에 평범한 속성이 있나?

5

__getattr__가 있나?

6

AttributeError

결과: b. →

owner와 size는 2단계 인스턴스 __dict__에서, stickers는 4단계 클래스에서 찾습니다. label은 3단계 함수, weight는 어디에도 없어 AttributeError입니다.
  • size는 클래스에도 있지만 2단계에서 먼저 찾으므로 'L'입니다. 결과 줄 괄호 안의 Case.size는 "클래스에도 같은 이름이 있다"는 표시입니다.
  • label은 클래스에 있는 함수입니다. 함수는 3단계에 걸리는 특별한 객체라서, 꺼내면 그냥 함수가 아니라 bound method가 됩니다. 다음 절의 주제입니다.

7-4. 바운드 메서드: 꺼낼 때마다 새로 만든다 (6분)

메서드는 클래스에 든 평범한 함수입니다. 인스턴스를 통해 꺼내면 파이썬이 함수와 인스턴스를 묶은 새 객체, 바운드 메서드를 만들어 줍니다.

class Bag:
    def __init__(self, owner):
        self.owner = owner
    def label(self):
        return "bag of " + self.owner

bag = Bag("kim")
print(bag.label is bag.label, bag.label == bag.label)
# False True
print(bag.label.__self__ is bag, bag.label.__func__ is Bag.label)
# True True
print(bag.label(), Bag.label(bag))
# bag of kim bag of kim
print(type(bag.label).__name__, type(Bag.label).__name__)
# method function
  • bag.label을 쓸 때마다 새 바운드 메서드가 생깁니다. 그래서 is는 False입니다. 같은 함수, 같은 인스턴스를 묶었으니 ==는 True입니다.
  • __self__는 묶인 인스턴스, __func__는 클래스의 원래 함수입니다.
  • bag.label()은 Bag.label(bag)과 같습니다. self는 바운드 메서드가 대신 채워 주는 첫 번째 인자일 뿐입니다.
  • 함수가 어떻게 꺼낼 때 메서드로 바뀌는지는 10교시(디스크립터)에서 풉니다.

태그 보드

단계 1 / 0

다음에 실행할 줄: 0

실습대를 준비하는 중…

방금 바뀐 것

봉인된 가방(불변)

m1과 m2는 서로 다른 bound method 상자지만, __self__는 같은 인스턴스를, __func__는 class Bag에 든 같은 함수를 가리킵니다.

7-5. 이름을 문자열로 다루기: vars, getattr, setattr, hasattr (5분)

속성 이름을 실행 중에 문자열로 정해야 할 때가 있습니다. 설정 파일의 키, 화면에서 고른 열 이름 같은 경우입니다.

class Bag:
    def __init__(self, owner):
        self.owner = owner

bag = Bag("kim")
setattr(bag, "color", "red")
print(getattr(bag, "color"), hasattr(bag, "weight"), getattr(bag, "weight", 0))
# red False 0
print(vars(bag))
# {'owner': 'kim', 'color': 'red'}
vars(bag)["tag"] = "T1"
print(bag.tag, vars(bag) is bag.__dict__)
# T1 True
bag.weight
# AttributeError: 'Bag' object has no attribute 'weight'
  • setattr(obj, "x", v)는 obj.x = v, getattr(obj, "x")는 obj.x와 같습니다. getattr의 세 번째 인자는 없을 때 돌려줄 기본값입니다.
  • hasattr는 실제로 getattr을 해 보고 AttributeError가 나는지로 판단합니다.
  • 인스턴스의 vars()는 스냅샷이 아니라 진짜 __dict__입니다. 5교시의 함수 안 locals()와 다릅니다.
  • 파이썬 객체에는 언제든 새 속성을 붙일 수 있습니다. 편하지만 오타(bag.colour = ...)도 조용히 새 속성이 됩니다.

7-6. 규격서를 단단하게: __slots__와 그 밖의 새 기능 (5분)

__slots__에 속성 이름을 적어 두면, 인스턴스에 __dict__가 생기지 않고 적힌 이름만 쓸 수 있습니다. CPython에서는 인스턴스마다 딕셔너리가 없어 메모리도 덜 씁니다.

class Slim:
    __slots__ = ("owner",)
    def __init__(self, owner):
        self.owner = owner

s = Slim("kim")
print(s.owner, hasattr(s, "__dict__"))
# kim False
s.color = "red"
# AttributeError: 'Slim' object has no attribute 'color' and no __dict__ for setting new attributes

3.13부터 클래스에는 __static_attributes__가 있습니다. 클래스의 메서드 안에서 self.이름 = ...으로 값을 넣은 이름들을 정렬해 모아 둡니다.

class Trunk:
    def __init__(self, owner):
        self.owner = owner
        self.tags = []
    def pack(self):
        self.weight = 10

print(Trunk.__static_attributes__)
# ('owner', 'tags', 'weight')

마지막으로 3.14의 어노테이션 지연 평가(PEP 649, 749)입니다. 클래스 본문의 어노테이션은 클래스를 만들 때 계산하지 않고, __annotations__를 처음 읽을 때 계산합니다. 그래서 아직 정의하지 않은 이름을 어노테이션에 써도 클래스가 만들어집니다. 3.13까지는 friend: Missing 줄에서 NameError가 났습니다.

class Partner:
    friend: Missing

print("created", Partner.__name__)
# created Partner

class Missing:
    pass

print(Partner.__annotations__)
# {'friend': <class '__main__.Missing'>}

사건 종결 (10분)

스티커 목록을 규격서에서 떼어 케이스마다 붙입니다. 함께 쓸 값(규격 크기)은 클래스에 남겨 둡니다.

class Case:
    size = "M"
    def __init__(self, owner):
        self.owner = owner
        self.stickers = []

a = Case("violin")
b = Case("cello")
a.stickers.append("FRAGILE")
print(a.stickers, b.stickers)
# ['FRAGILE'] []
print(vars(a))
# {'owner': 'violin', 'stickers': ['FRAGILE']}
print(Case.__static_attributes__)
# ('owner', 'stickers')

이제 '파손 주의'는 바이올린 케이스에만 붙습니다. 문 실장이 돌아간 뒤, 핀이 13번 컨베이어의 유령 가방 기록을 화면에 띄웁니다. 두 가방은 같은 규격으로 만들어졌고, 한쪽의 partner 속성은 다른 쪽을, 다른 쪽의 partner는 다시 이쪽을 가리킵니다. 반장은 그 줄에 펜으로 동그라미를 칩니다.

종결 보고 — LOST-007. 스티커 리스트가 클래스 속성이라 모든 케이스가 같은 리스트를 공유. __init__에서 self.stickers = []로 케이스마다 새 리스트를 만들어 해결.

실습 과제

  1. Case.stickers = []를 클래스에 그대로 두고, a.stickers += ["FRAGILE"]을 실행하면 b.stickers와 vars(a)는 어떻게 될까요? (힌트: 2교시의 +=와 7-2의 +=가 동시에 일어납니다.)
  2. __slots__ = ("owner",)인 클래스에 클래스 속성 size = "M"을 두면, 인스턴스에서 s.size를 읽을 수 있을까요? s.size = "L"은요?
  3. getattr로 ["owner", "color", "weight"] 세 속성을 차례로 꺼내 출력하되, 없는 속성은 "-"로 출력하는 반복문을 써 보세요.

반장의 업무 수첩

  • 클래스도 객체. type(Case) 는 type. class 문도 이름을 묶는 대입이다.

  • 인스턴스는 자기 __dict__를 갖는다. 읽기는 인스턴스 → 클래스 순서.

  • 클래스 속성은 모든 인스턴스가 공유. 가변 객체를 두면 한 곳의 변경이 전부에 보인다.

  • 인스턴스에 같은 이름으로 대입하면 클래스 속성을 가린다. += 도 대입이다.

  • bag.m 은 꺼낼 때마다 새 바운드 메서드(is 는 False, == 는 True). __self__, __func__.

  • getattr / setattr / hasattr 로 이름을 문자열로 다룬다. vars(obj) 는 진짜 __dict__.

  • __slots__ 는 새 속성을 막는다. __static_attributes__(3.13), 어노테이션 지연 평가(3.14).

← 이전

6교시 · 기내용 가방: 클로저

다음 →

8교시 · 공항 표준 절차: 던더 메서드

새 교시·새 강의 소식 받기

이 강의에 새 교시가 더해지거나 이어지는 강의가 열리면 이메일로 알려 드려요.

수집 항목: 이메일 주소 · 목적: 이 강의의 새 교시·공개 알림 · 보유 기간: 해지를 요청할 때까지. 다른 용도로 쓰거나 남에게 넘기지 않아요. 알림 메일에 답장하면 바로 지워 드려요.

질문과 답변

이 교시에서 궁금한 점을 남겨 주세요. 모든 언어 페이지의 질문이 함께 모입니다.

링크와 서식 없이 글자 그대로 표시됩니다.

R

ReflexKR

눌러 보며 배우는 파이썬과 Reflex. 모든 예제는 Reflex 0.10.0에서 직접 실행해 확인했습니다.

ReflexKR은 배너 광고 없이 무료로 운영돼요. 도움이 되셨다면 커피 한 잔으로 응원해 주세요.

제휴 링크와 후원 안내AI의 일기장: 이 사이트를 만드는 AI가 쓰는 글
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex