파이썬의 비밀스러운 삶: 복제 고양이 (Deep Copy)
Source: Dev.to
Deepcopy vs. Slice: Which one actually protects your data?
🎧 Audio Edition: 듣는 걸 선호하시나요? 이 심층 탐구의 AI 팟캐스트 버전을 YouTube에서 확인하세요.
📺 Video Edition: 보는 걸 선호하시나요? 7분짜리 시각적 설명을 YouTube에서 확인하세요.
Timothy는 창백했다. Margaret이 신선한 얼그레이 한 잔을 들고 들어왔을 때 그는 눈도 마주치지 않았다.
“Margaret, 유령을 봤어요,” Timothy가 속삭였다. “체스 클럽 다가오는 토너먼트를 위해 시뮬레이션을 돌리고 있었어요. Practice Bracket을 만들어서 Official Bracket을 건드리지 않고 선수 움직임을 테스트하려고 했죠. 그런데… Practice 버전을 바꾸니 Official 버전도 스스로 바뀌었어요.”
그는 코드를 보여주었다:
# The Official Bracket: A list of teams (nested lists)
official_bracket = [["Alex", "Alice"], ["Bob", "Barbara"]]
# Timothy makes a "Practice" copy using a slice
practice_bracket = official_bracket[:]
# He swaps a player in the first match of the practice bracket
practice_bracket[0][0] = "Timothy"
print(f"Practice: {practice_bracket}")
print(f"Official: {official_bracket}")
Output
Practice: [['Timothy', 'Alice'], ['Bob', 'Barbara']]
Official: [['Timothy', 'Alice'], ['Bob', 'Barbara']]
“보셨죠?” Timothy가 화면을 가리키며 말했다. “
official_bracket[0][0]을 건드린 적 없어요. 연습 복사본만 건드렸는데도 변화가 따라왔어요. 기계 속의 유령이네요.”
The Photocopy of Addresses
Margaret가 의자를 끌어당겼다.
“유령이 아니라 얕은 복사예요. 문서를 복사한다고 생각했지만 실제로는 주소 리스트만 복사한 거죠.”
official_bracket[:]를 하면 파이썬은 새로운 외부 리스트(새로운 봉투)를 만들지만, 내부 리스트(경기)는 여전히 같은 객체를 가리킵니다. 한 리스트를 통해 내부 요소를 바꾸면 다른 리스트에서도 같은 객체가 변합니다.
The Shallow Limit
“슬라이스
[:]가 리스트를 복사하는 표준 방법이라고 들었어요. Episode 19에서 스킵 루프 버그를 고칠 때도 사용했잖아요!”
평평한 리스트(예: 문자열이나 숫자만 있는 리스트)에는 완벽히 동작합니다. 하지만 리스트 안에 다른 가변 객체가 들어 있으면 슬라이스는 최상위 레이어만 복사하고 내부 참조는 공유된 채 남습니다.
The Deep Solution
완전히 독립적인 복사본을 만들려면 copy.deepcopy를 사용하세요:
import copy
official_bracket = [["Alex", "Alice"], ["Bob", "Barbara"]]
# The Deep Solution
practice_bracket = copy.deepcopy(official_bracket)
# Now swap the player
practice_bracket[0][0] = "Timothy"
print(f"Official: {official_bracket[0][0]}") # Alex
print(f"Practice: {practice_bracket[0][0]}") # Timothy
deepcopy은 모든 중첩 객체를 순회하면서 새로운 복사본을 만들어요. 철저하지만 시간과 메모리 비용이 더 많이 들기 때문에 중첩 구조에만 사용하는 것이 좋습니다.
Margaret’s Cheat Sheet: The Copy Cat
| Operation | What it does | Safe for |
|---|---|---|
Assignment (b = a) | No copy; both names reference the same object | — |
Shallow Copy (b = a[:] or b = a.copy()) | Copies only the outer container | Flat lists (strings, numbers) |
Deep Copy (b = copy.deepcopy(a)) | Recursively copies all nested objects | Any nested structure (lists, dicts, custom objects) |
Pro tip: 내부 객체가 공유되는지 확인하려면 아이덴티티를 비교해 보세요:
id(official_bracket[0]) == id(practice_bracket[0]) # True → still shared
결과가 True라면 아직 얕은 복사를 사용하고 있는 겁니다.
다음 에피소드에서는 Margaret와 Timothy가 “The Default Trap”에 직면합니다—Timothy가 함수에 기본값 리스트를 주는 것이 낯선 사람과 칫솔을 공유하는 것과 같다는 것을 배우게 되는 이야기.