리스트를 복사했는데 원본까지 같이 바뀐 이유 — 얕은 복사의 함정
문제 발생
2차원 리스트를 복사해서 사본만 수정하려고 했는데, 원본 리스트까지 함께 바뀌는 문제가 발생했습니다.
original = [[1, 2], [3, 4]]
copied = original.copy() # 또는 original[:], list(original)
copied[0][0] = 999
print(original) # [[999, 2], [3, 4]] — 원본도 바뀜원인 분석
.copy(), 슬라이싱([:]), list(original)은 모두 **얕은 복사(shallow copy)**입니다 — 바깥쪽 리스트만 새로 만들고, 그 안의 원소들은 원본과 똑같은 객체를 그대로 참조합니다.
original과 copied는 서로 다른 리스트 객체지만, original[0]과 copied[0]은 같은 내부 리스트 [1, 2]를 가리키는 같은 참조입니다. copied[0][0] = 999는 그 공유된 내부 리스트 자체를 수정하는 것이므로, original[0]을 통해 봐도 똑같이 바뀐 것처럼 보입니다.
원소가 문자열, 숫자처럼 불변(immutable) 타입뿐이라면 얕은 복사로도 이 문제가 발생하지 않습니다 — 불변 객체는애초에 내용을 직접 바꿀 방법이 없어서, 항상 새 객체를 만들어 재할당하는 형태로만 값이 바뀌기 때문입니다. 문제는 리스트, 딕셔너리, 커스텀 객체처럼 가변(mutable) 원소를 담고 있을 때만 나타납니다.
해결 방안
중첩된 가변 구조를 진짜로 독립적인 사본으로 만들려면 **깊은 복사(deep copy)**를 씁니다.
import copy
original = [[1, 2], [3, 4]]
copied = copy.deepcopy(original)
copied[0][0] = 999
print(original) # [[1, 2], [3, 4]] — 원본은 그대로copy.deepcopy()는 재귀적으로 내부의 모든 가변 객체까지 새로 복제합니다.
- 항상 깊은 복사가 정답인 것은 아닙니다 — 깊은 복사는 얕은 복사보다 비용이 크고, 원본과 사본이 의도적으로 내부 객체를 공유해야 하는 설계라면 얕은 복사가 오히려 맞을 수 있습니다. "이 복사가 정말로 완전히 독립적이어야 하는가"를 먼저 판단합니다.
- 커스텀 클래스는
__deepcopy__메서드를 재정의해 깊은 복사 동작을 직접 제어할 수 있습니다 — 특정 필드(예: DB 커넥션 핸들 같은 것)는 복사하지 않고 공유해야 하는 경우에 유용합니다. deepcopy는 순환 참조가 있는 구조도 안전하게 처리합니다(내부적으로 이미 복사한 객체를 기억해 무한 루프를 피합니다) — 직접 재귀 복사 로직을 짜는 것보다 안전합니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.