
파이썬 얕은 복사와 깊은 복사 차이는 입문자에게 아주 자주 헷갈리는 주제입니다. list를 복사한 줄 알았는데 원본도 같이 바뀌면, 문법보다 먼저 “파이썬이 이상하다”는 느낌이 들기 쉽습니다.
하지만 원리는 단순합니다. 파이썬은 복사처럼 보여도 실제로는 참조를 공유하는 경우가 많고, 바깥 컨테이너만 새로 만드는지, 안쪽 객체까지 새로 만드는지에 따라 결과가 달라집니다.
가장 먼저 알아야 할 것: 대입은 복사가 아니다
a = [1, 2, 3]
b = a
b.append(4)
print(a) # [1, 2, 3, 4]Python 공식 문서도 assignment는 copy가 아니라 binding이라고 설명합니다. 즉 b = a는 새 리스트를 만든 것이 아니라 같은 객체를 같이 가리키게 만든 것입니다.
얕은 복사는 어디까지 새로 만들까
a = [[1, 2], [3, 4]]
b = a.copy()
b[0].append(99)
print(a) # [[1, 2, 99], [3, 4]]
print(b) # [[1, 2, 99], [3, 4]]얕은 복사는 바깥 리스트는 새로 만들지만, 안쪽 리스트는 같은 참조를 그대로 들고 있습니다. 그래서 nested list처럼 안쪽에 mutable object가 있으면 같이 바뀌는 것처럼 보입니다.
- 바깥 컨테이너는 새로 만든다
- 안쪽 객체 참조는 그대로 가져온다
- 그래서 중첩 구조에서 버그가 자주 난다
깊은 복사는 무엇이 다를까
import copy
a = [[1, 2], [3, 4]]
b = copy.deepcopy(a)
b[0].append(99)
print(a) # [[1, 2], [3, 4]]
print(b) # [[1, 2, 99], [3, 4]]깊은 복사는 안쪽 객체까지 재귀적으로 복사합니다. 그래서 nested structure를 독립적으로 다뤄야 할 때 더 안전합니다. 다만 공식 문서도 말하듯이 deep copy는 너무 많이 복사할 수 있고, 순환 참조나 사용자 정의 클래스에서는 별도 고려가 필요합니다.
list slicing, list.copy, copy.copy는 어떻게 볼까
입문 단계에서는 이 셋을 대부분 “얕은 복사” 계열로 이해해도 괜찮습니다. 즉 바깥 구조는 새로 만들지만, 안쪽 mutable object는 여전히 공유될 수 있습니다.
a = [[1], [2]]
b = a[:]
c = a.copy()
import copy
d = copy.copy(a)차이를 외우기보다, nested mutable object가 있으면 얕은 복사에서는 같이 흔들릴 수 있다는 감각을 먼저 잡는 편이 더 중요합니다.
실무에서 자주 나는 버그
- 기본 템플릿 리스트를 복사해 쓴 줄 알았는데 원본까지 오염되는 경우
- 2차원 리스트를 복사한 뒤 한 행만 수정했는데 다른 곳도 같이 바뀌는 경우
- 함수에서 받은 nested dict/list를 안전하게 분리하지 않고 수정하는 경우
그래서 복사 버그는 문법 실수라기보다, 참조 공유를 눈에 보이지 않게 지나친 결과인 경우가 많습니다.
기초 감각은 이미 발행한 기존 얕은 복사/깊은 복사 글, 반복문 가독성은 enumerate 글과도 연결됩니다.
마무리
파이썬 얕은 복사와 깊은 복사 차이는 결국 참조를 어디까지 공유하느냐의 차이입니다. 대입은 복사가 아니고, 얕은 복사는 바깥만 새로 만들고, 깊은 복사는 안쪽 객체까지 새로 만듭니다.
이 원리만 확실히 잡으면 “왜 같이 바뀌지?” 같은 복사 버그는 훨씬 덜 당황스럽게 보이기 시작합니다.