Python의 메모리 모델은 당신이 생각하는 것과 다릅니다
요약
Python의 변수는 값을 담는 상자가 아니라 객체에 이름을 붙이는 바인딩(Binding) 방식임을 설명합니다. 이를 통해 변이(Mutation)와 재바인딩(Rebinding)의 차이를 이해하고 메모리 모델을 정확히 파악할 수 있습니다.
핵심 포인트
- Python 변수는 객체에 붙은 라벨(Label) 역할을 함
- 할당은 새로운 바인딩을 생성하고, 변이는 기존 객체를 수정함
- 가변 타입의 복합 할당(+=)은 객체를 제자리에서 수정함
- 불변 타입의 복합 할당은 새로운 객체를 생성하여 재바인딩함
Python의 메모리 모델은 당신이 생각하는 것과 다릅니다
대부분의 Python 개발자에게 Python이 변수를 어떻게 저장하는지 물으면 그들은 "값을 저장합니다"라고 대답할 것입니다. 이는 실제 버그를 유발하고 인터뷰에서 혼란을 야기하는 부정확한 방식입니다. Python이 데이터를 저장하고 검색하는 방식에 대한 정확한 멘탈 모델 (Mental Model)은 코드를 읽고 쓰는 방식을 변화시킵니다.
Python은 변수에 값을 저장하지 않습니다. Python은 이름을 객체 (Object)에 바인딩 (Binding) 합니다.
이러한 구분은 변이 (Mutation), 함수 인자 (Function Arguments), 또는 에일리어싱 (Aliasing)이 포함된 코드를 추적하기 전까지는 철학적으로 들릴 수 있습니다. 하지만 일단 추적하기 시작하면 이 개념은 언어에서 가장 실용적으로 유용한 개념이 됩니다.
이름은 상자가 아닙니다
변수는 값을 담는 상자라는 상자 비유 (Box metaphor)는 대부분의 입문 프로그래밍 과정에서 변수를 설명하는 방식입니다. 많은 언어에서 이 비유는 문제가 발생하지 않을 정도로 충분히 정확합니다.
하지만 Python에서는 중요한 측면에서 이 비유가 틀렸습니다.
더 정확한 비유는 다음과 같습니다: Python의 이름은 객체에 붙은 라벨 (Label)입니다. 객체는 메모리 (Memory)에 독립적으로 존재합니다. 여러 개의 라벨이 동일한 객체에 붙을 수 있습니다. 새로운 라벨을 붙인다고 해서 객체가 이동하거나 복사되지 않습니다.
x = [1, 2, 3]
y = x
print(id(x) == id(y)) # True (동일한 객체, 두 개의 라벨)
...
y = x라고 작성할 때, 당신은 리스트를 복사하는 것이 아닙니다. 정확히 동일한 리스트 객체를 가리키는 두 번째 라벨을 생성하는 것입니다.
반드시 구분해야 할 네 가지 연산
1. 할당 (Assignment)은 새로운 바인딩을 생성합니다
x = [1, 2, 3]
x = [4, 5, 6] # 이제 x는 완전히 다른 객체를 라벨링합니다
첫 번째 리스트는 가비지 컬렉션 (Garbage Collected)될 때까지 메모리에 여전히 존재합니다. 이름 x는 단순히 그것을 가리키는 것을 멈추고 이제 두 번째 리스트를 가리킵니다.
2. 변이 (Mutation)는 기존 객체를 수정합니다
x = [1, 2, 3]
x.append(4) # x가 라벨링하는 객체가 제자리에서(in place) 수정됩니다
동일한 객체를 가리키는 다른 모든 이름은 즉시 이 변경 사항을 반영할 것입니다. 왜냐하면 그들이 동일한 메모리 위치를 바라보고 있기 때문입니다.
3. 가변 타입 (Mutable types)에 대한 복합 할당 (Augmented assignment)은 변이합니다
x = [1, 2, 3]
y = x
x += [4, 5]
...
리스트의 += 연산자는 내부적으로 __iadd__를 호출하며, 이는 새로운 컬렉션을 생성하는 대신 리스트를 제자리에서 확장(in place)합니다.
4. 불변 타입 (Immutable types)에서의 복합 할당은 재바인딩 (Rebinds) 합니다
x = 5
y = x
x += 1
...
정수(Integer)는 불변(Immutable)입니다. x += 1은 정수 객체 5를 수정할 수 없습니다. 대신, 결과값을 계산하여 완전히 새로운 정수 객체 6을 생성하고, x를 이 새로운 객체에 재바인딩 (Rebind) 합니다. y는 영향을 받지 않은 채 여전히 5를 가리키고 있습니다.
이것이 함수 호출에서 중요한 이유
Python이 객체를 처리하는 방식은 데이터가 함수로 어떻게 들어오고 나가는지를 정확하게 결정합니다:
def process(data):
data = data + [99] # 새로운 리스트를 생성하고, 로컬 이름을 재바인딩합니다
return data
...
유일한 구조적 차이는 data = data + [99]와 data += [99]의 차이입니다. 하나는 새로운 객체를 생성하고 로컬 레이블을 업데이트합니다. 다른 하나는 공유된 기반 객체를 변이 (Mutate) 시킵니다. 겉보기에는 동일해 보이지만, 원래 변수와 관련하여 완전히 다른 동작을 생성합니다.
이로 인해 발생하는 면접 질문
면접관들은 정확한 멘탈 모델 (Mental model)을 갖추어야 예측할 수 있는 방식으로 이러한 동작들을 조합하여 질문합니다:
def modify(items, value=[]):
value.append(items)
return value
...
이 출력을 정확하게 예측하려면, 기본 인자 (Default argument)인 value=[]가 함수 정의 시점에 정확히 하나의 리스트 객체를 생성한다는 점을 이해해야 합니다. .append() 메서드는 이후의 모든 호출에 걸쳐 이 단일하고 지속적인 객체를 변이 (Mutate) 시킵니다.
출력:
[1]
[1, 2]
[1, 2, 3]
...
만약 이 문제를 확신을 가지고 맞혔다면, Python의 실행 및 저장 시스템에 대한 당신의 멘탈 모델은 정확한 것입니다.
연습 코딩 문제
코드 추적 (Code tracing) 정확도를 높이고 싶다면, 코드를 실행하기 전에 이러한 로직 흐름을 수동으로 실행해 보세요.
Python의 메모리 모델을 대상으로 하는 연습 문제들은 PyCodeIt에서 확인할 수 있으며, 특히 가변 객체 (mutable objects) 및 함수 인자 (function arguments)를 포함하는 Medium 및 Hard 난이도의 드라이 런 (dry-run) 문제들이 준비되어 있습니다. pycodeit.com에서 여러분의 실력을 테스트해 보세요.
기술 면접 준비를 위한 무료 AI 기반 Python 드라이 런 (dry-run) 연습 플랫폼인 PyCodeIt의 개발자가 작성하였습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기