깊은 복사와 얕은 복사 - 파이썬 새로운 객체 만드는 방법
파이썬의 깊은 복사와 얕은 복사
일반적으로 프로그래밍을 하다가 잦은 실수는 깊은 복사와 얕은 복사의 차이를 이해하지 못하는 부분에서 발생한다. 주로 파이썬 언어의 경우, 기존 C언어에서 사용되는 '포인터'라는 개념이 없이, 모두 객체로 구성되기 때문에 혼동하기 쉽다.
따라서 오늘은 파이썬의 깊은 복사와 얕은 복사에 대해 알아보고자 한다.
깊은 복사와 얕은 복사의 개념
깊은 복사와 얕은 복사는 모두 객체를 복제하는 방법을 의미하지만, 복제된 객체의 참조 방식에서 중요한 차이점을 지닌다.
- 얕은 복사 (Shallow Copy): 원본 객체의 최상위 레벨의 '참조'를 복사한다. 즉, 복사된 객체는 원본 객체와 같은 메모리 주소를 공유하는 내부 요소를 포함하게 된다. 따라서 얕은 복사된 객체에서 내부 요소를 수정할 경우, 해당 수정 사항은 원본 객체에도 영향을 미치게 된다. 예를 들어, 리스트 내부에 중첩된 리스트가 있는 경우 바깥 리스트만 복사되고 내부 리스트는 원본을 참조하게 된다. 이는 얕은 복사가 객체의 최상위 레벨만 복사하고, 내부에 중첩된 객체들은 동일한 참조를 유지하는 특성 때문이다.
- 깊은 복사 (Deep Copy): 원본 객체와 완전히 독립적인 객체를 생성한다. 복사된 객체는 원본과 동일한 구조와 데이터를 가지지만, 메모리 주소가 달라서 원본과 복사된 객체 사이에 어떠한 참조 관계도 없다. 즉, 깊은 복사는 객체 내의 모든 중첩된 구조를 재귀적으로 복사하기 때문에, 원본 객체의 수정이 복사된 객체에 영향을 미치지 않도록 보장한다.
깊은 복사와 얕은 복사가 존재하는 이유: 메모리 관점에서
깊은 복사와 얕은 복사는 메모리 효율성과 성능 측면에서 서로 다른 목적을 가진다.
- 얕은 복사는 원본 객체의 데이터 참조를 공유하기 때문에 메모리 사용량을 줄일 수 있다. 중첩된 객체를 복사할 필요 없이 원본 객체의 주소만 복사함으로써 메모리 공간을 절약하고 복사 작업의 속도를 높일 수 있다. 이는 대용량 데이터 구조를 처리할 때 특히 유용하다. 그러나 이러한 공유 때문에 원본과 복사된 객체가 서로 영향을 주게 되어 데이터의 일관성을 유지하기 어려운 상황이 발생할 수 있다.
- 깊은 복사는 이러한 문제를 해결하기 위해 모든 데이터를 독립적으로 복사하여 완전히 분리된 객체를 생성한다. 이는 원본과 복사본 사이의 참조 관계를 제거함으로써 각 객체가 독립적으로 동작하게 만들어 데이터 안정성을 높인다. 그러나 이 방식은 메모리 사용량이 증가하고, 중첩된 객체 구조가 복잡할수록 복사 작업의 시간적 비용도 커진다는 단점이 있다. 따라서 깊은 복사와 얕은 복사는 메모리와 성능, 그리고 데이터의 일관성 유지 사이의 균형을 맞추기 위해 필요하다.
파이썬에서 얕은 복사와 깊은 복사를 수행하는 방법
파이썬에서는 다양한 방식으로 얕은 복사와 깊은 복사를 수행할 수 있다. 이를 각각 살펴보자.
얕은 복사 방법
- 슬라이싱 사용: 리스트의 경우 슬라이싱을 통해 얕은 복사를 할 수 있다.슬라이싱은 리스트의 얕은 복사를 수행하며, 원본 리스트의 참조 관계는 유지된다.
original_list = [1, 2, [3, 4]]
shallow_copy = original_list[:]
copy()
메서드 사용: 리스트나 딕셔너리의 경우copy()
메서드를 통해 얕은 복사를 수행할 수 있다.copy()
메서드는 객체의 최상위 레벨만 복사하며, 중첩된 객체는 동일한 참조를 갖는다.original_list = [1, 2, [3, 4]] shallow_copy = original_list.copy()
copy
모듈의copy()
함수 사용: 파이썬의copy
모듈에 있는copy()
함수는 명시적으로 얕은 복사를 수행한다.import copy original_list = [1, 2, [3, 4]] shallow_copy = copy.copy(original_list)
깊은 복사 방법
깊은 복사를 수행하기 위해서는 파이썬의 copy
모듈을 사용해야 한다.
copy
모듈의deepcopy()
함수 사용:import copy original_list = [1, 2, [3, 4]] deep_copy = copy.deepcopy(original_list)
deepcopy()
함수는 객체와 그 내부의 모든 중첩된 요소들까지 재귀적으로 복사하여 원본과 완전히 독립된 복사본을 만들어낸다.
얕은 복사와 깊은 복사의 차이: 예시
다음 예시를 통해 얕은 복사와 깊은 복사의 차이를 보다 명확히 이해할 수 있다.
import copy
original_list = [1, 2, [3, 4]]
shallow_copy = copy.copy(original_list)
deep_copy = copy.deepcopy(original_list)
# 내부 리스트의 값을 수정해보자
original_list[2][0] = 99
print("원본 리스트:", original_list) # [1, 2, [99, 4]]
print("얕은 복사된 리스트:", shallow_copy) # [1, 2, [99, 4]]
print("깊은 복사된 리스트:", deep_copy) # [1, 2, [3, 4]]
위의 코드에서 original_list
의 내부 리스트 요소를 수정하면, 얕은 복사된 shallow_copy
에서도 동일하게 값이 변경된 것을 볼 수 있다. 이는 얕은 복사가 내부 객체의 참조를 공유하기 때문이다. 반면, 깊은 복사된 deep_copy
는 원본의 변경에 영향을 받지 않으며, 이는 깊은 복사가 객체를 재귀적으로 독립적으로 복사했기 때문이다.
그림으로 설명을 하면 아래와 같다.


결론
얕은 복사는 객체의 최상위 레벨만 복사하여 내부 객체의 참조를 유지함으로써 원본과 복사본이 서로 영향을 주고받게 한다.
반면, 깊은 복사는 객체 전체를 재귀적으로 복사하여 원본과 복사본이 완전히 독립적으로 존재하도록 한다. 파이썬에서는 copy
모듈을 통해 이러한 복사 작업을 쉽게 수행할 수 있으며, 얕은 복사(copy()
)와 깊은 복사(deepcopy()
)의 개념을 잘 이해하여 올바르게 사용하는 것이 데이터 구조 관리에서 매우 중요하다.
깊은 복사와 얕은 복사의 존재 이유는 메모리 사용과 성능의 균형을 맞추기 위한 것이다. 얕은 복사는 메모리 사용량을 줄이고 복사 속도를 높이지만, 데이터 일관성을 해칠 수 있다. 깊은 복사는 독립성을 보장하지만 메모리와 시간적 비용이 더 든다. 따라서, 사용 상황에 따라 이 둘을 적절히 선택하는 것이 중요하다.