할당은 *바인딩* 이지 복사가 아니야
Lesson 1 에서 지나가듯 말한 거 — *이름은 라벨, 타입은 값에 살아*. 이 lesson 이 그 mental model 을 *load-bearing* 으로 바꿔. 한 번 내재화하면, 커리어 절반의 버그가 증발해. 잘못 이해하면 — 몇 달 단위로 mutability 사고에 계속 물려.
x = 42 라고 쓰면, Python 이 x 라는 *상자* 를 만들어서 42 를 그 안에 넣는 게 아니야. Python 이 *값이 42 인 정수 객체* 를 만들고, 이름 x 가 그 객체를 가리키게 해. 이름과 객체는 *다른 것* — 그리고 *같은 객체* 를 *여러 이름* 이 가리킬 수 있어.
Mutable vs immutable — 언어 전체를 가로지르는 분리선
모든 Python 값은 *mutable* (내용 in-place 변경 가능) 또는 *immutable* (불가능) 둘 중 하나. 분리는 타입으로 고정:
- Immutable — int, float, str, bool, tuple, frozenset, bytes
- Mutable — list, dict, set, bytearray, 그리고 명시적으로 frozen 안 한 모든 클래스 인스턴스
왜 중요한가 — 두 이름이 *같은 mutable 객체* 를 가리키면, 한 이름으로 변경한 게 다른 이름으로 보여. 객체는 *하나뿐*. 두 이름이 *같은 immutable 객체* 를 가리키면, 변경 자체가 불가 — 변경처럼 보이는 연산 (예 — int 의 x += 1) 은 사실 *새 객체 만들고 이름 다시 가리키게* 함.
a = [1, 2, 3]; b = a; b.append(4). 이제 a 도 [1, 2, 3, 4] 야. 왜냐면 a 와 b 는 *두 개의 list* 였던 적이 없어. 항상 *이름 두 개를 가진 list 하나* 였어. 분리된 list 가 필요하면 명시적으로 — b = a.copy() (또는 b = list(a), 또는 b = a[:]).
== vs is — 동등성 vs 정체성
비슷해 보이고 *완전히 다른 의미* 의 두 연산자:
==는 동등성 검사 — *"두 값이 같다고 비교되나?"* 내부적으로__eq__호출, 클래스가 override 가능.is는 정체성 검사 — *"두 이름이 메모리의 *문자 그대로 같은 객체* 를 가리키나?"* override 불가.
경험칙 — 값 비교는 ==, *singleton* 비교 (None, True, False) 는 is. PEP 8 가 codify 한 것 — if x is None:, *절대* if x == None: 안 함. 결과는 같지만, is None 이 idiom.
id() — 객체 식별자
Python built-in id(obj) 가 객체의 lifetime 동안 유일한 정수 반환. CPython 은 메모리 주소로 구현. a is b 는 id(a) == id(b) 와 동치.
production 코드에선 id() 거의 안 써. 근데 *학습 중* 이해 도구로는 무가치. *"내 연산 후에 이게 여전히 같은 객체인가?"* — 전후로 id() 출력해.
다중 할당과 튜플 언패킹
Python 은 한 statement 에서 여러 이름 할당을 허용. 오른쪽이 *튜플로 먼저* 평가되고, 왼쪽 이름들로 *언패킹*. 가장 idiomatic Python 패턴 일부의 기반.
대표 — 변수 스왑. 대부분 언어에선 a 와 b 스왑이 임시 변수 필요. Python 에선 — a, b = b, a. 오른쪽이 튜플 (b, a) 가 되고, 다시 a, b 로 언패킹. 원자적, 임시 변수 없음.
session_id, message_id = await create_pair(), brain, model = pick_brain_for_task(task), x, y, w, h = avatar_box. 영어처럼 읽혀 — *"이 두 가지에 이름 붙여, 값은 여기."* 그 readability 가 Pythonic 의 보상.
Augmented 할당 — mutable 의 깜짝 효과
x += 1 이 단일 연산자로 보이는데, 의미는 왼쪽이 mutable 인지에 달려있어. immutable 타입 (int, str, tuple) 에선 x += 1 이 x = x + 1 과 *정확히 동치* — Python 이 새 객체 만들고 이름 다시 가리킴.
mutable 타입 (list, dict, set) 에선 x += y 가 정의돼있으면 __iadd__ 호출, x 를 *in-place 변경*. list += other 는 in-place append, list = list + other 는 새 list 생성. 비슷해 보여, 같지 않아.
*"default mutable 인자"* 함정 (preview)
이건 위 모든 걸 합쳐서 Python 의 가장 유명한 초보자 함정. Flow 트랙에서 제대로 다뤄, 지금은 *모양만* 봐:
def foo(items=[]): 라고 쓰고 인자 없이 세 번 호출하면, 세 호출이 *같은* list 를 공유한다는 걸 발견. default 인자는 함수 *정의* 시점에 *한 번* 평가, 호출마다 X. mutable default 가 호출 사이에 살아남아.
해결 — def foo(items=None): 그리고 안에서 if items is None: items = []. Flow 트랙이 *왜* 를 설명. 지금은 그 *냄새* 만 알아봐.
요약
이름이 객체를 가리켜. 어떤 객체는 mutable, 어떤 건 아니. 두 이름이 같은 mutable 객체를 가리킬 수 있어. == 는 값 비교, is 는 정체성 비교. 이 네 문장이 자명하게 느껴지는 시점부터, Python 의 나머지 동작이 더 이상 안 놀라워.
is None / is not None, *절대* == None 안 함. throw-away 변수 도입 전에 튜플 언패킹부터 손이 가는 게 맞아. *"안전 위해"* list copy 할 때, *진짜로 copy 가 필요한지* 아니면 *새 이름이 필요한 건지* 정직해야 — copy 가 싸지만 공짜는 아니고, 불필요한 copy 는 진짜 의도를 가려.