- [Python] 객체 복사: 참조 할당, 얕은 복사, 깊은 복사 완벽 이해하기2025년 07월 28일 12시 58분 26초에 업로드 된 글입니다.작성자: DandyNow728x90반응형
파이썬에서 객체 복사: 참조 할당, 얕은 복사, 깊은 복사 완벽 이해하기
파이썬을 사용하다 보면 리스트나 딕셔너리 같은 객체를 다룰 때 예상치 못한 결과에 당황할 때가 있다. 특히 객체를 복사하는 과정에서 '어라? 원본을 바꿨는데 복사본도 바뀌었네?' 혹은 '복사본을 바꿨는데 원본이 왜 안 바뀌지?' 같은 경험을 해본 적이 있을 것이다. 이는 파이썬의 참조 할당, 얕은 복사, 깊은 복사 개념을 정확히 이해하지 못했기 때문이다.
이 세 가지 개념을 명료한 예제와 함께 자세히 알아보자.
1. 참조 할당 (Reference Assignment)
가장 기본적인 형태의 '복사'처럼 보이지만, 사실은 복사가 아닌 같은 객체를 가리키게 하는 것이다.
원리와 특징
- 원리: 한 변수가 가리키는 객체의 메모리 주소 자체를 다른 변수에 할당한다.
- 결과: 두 변수는 메모리 상의 동일한 하나의 객체를 참조한다.
- 특징: 한 변수를 통해 객체를 변경하면, 다른 변수도 동일한 객체를 가리키고 있으므로 변경 사항이 그대로 반영된다.
💡 핵심 포인트: 이는 실제로는 "복사"가 아니라 "별명 만들기"에 가깝다. 마치 한 사람을 "철수"라고도 부르고 "형"이라고도 부르는 것과 같다.
예제
original_list = [1, 2, 3] assigned_list = original_list # 참조 할당 print(f"Original ID: {id(original_list)}") print(f"Assigned ID: {id(assigned_list)}") print(f"두 변수는 동일한 객체인가? {original_list is assigned_list}") original_list.append(4) # original_list를 변경 print(f"original_list 변경 후: {original_list}") print(f"assigned_list: {assigned_list}") # assigned_list도 함께 변경됨출력 결과:
Original ID: 140234567890123 Assigned ID: 140234567890123 두 변수는 동일한 객체인가? True original_list 변경 후: [1, 2, 3, 4] assigned_list: [1, 2, 3, 4]2. 얕은 복사 (Shallow Copy)
원본 객체와는 별개의 새로운 객체를 만들지만, 그 내부의 중첩된 객체(가변 객체)들은 여전히 원본과 공유하는 방식이다.
원리와 특징
- 원리: 새로운 객체를 생성하고, 원본 객체의 1단계 하위 요소들에 대한 참조(주소)만 복사한다.
- 사용 방법:
- 리스트:
new_list = old_list[:],new_list = old_list.copy(),new_list = list(old_list),new_list = [*old_list] - 딕셔너리:
new_dict = old_dict.copy(),new_dict = dict(old_dict),new_dict = {**old_dict} - 범용:
copy모듈의copy.copy()함수 사용
- 리스트:
- 결과:
- 외부 객체: 원본과 복사본은 서로 다른 독립적인 객체이다.
- 내부 가변 객체: 만약 리스트 안에 리스트(중첩 리스트)처럼 가변 객체가 있다면, 그 가변 객체들은 원본과 복사본이 여전히 동일한 것을 참조한다.
⚠️ 주의사항: 얕은 복사는 "1단계 깊이"까지만 새로운 객체를 만든다. 마치 상자는 새로 만들었지만, 상자 안의 작은 상자들은 여전히 원본과 공유하는 것과 같다.
예제
import copy original_nested_list = [1, 2, [3, 4]] shallow_copied_list = original_nested_list[:] # 얕은 복사 print(f"Original ID: {id(original_nested_list)}") print(f"Shallow Copied ID: {id(shallow_copied_list)}") print(f"두 변수는 동일한 객체인가? {original_nested_list is shallow_copied_list}") print(f"원본 내부 리스트 ID: {id(original_nested_list[2])}") print(f"복사본 내부 리스트 ID: {id(shallow_copied_list[2])}") print(f"내부 리스트는 동일한 객체인가? {original_nested_list[2] is shallow_copied_list[2]}") original_nested_list.append(5) # 외부 리스트 변경: 서로 영향 없음 print(f"\n외부 리스트 변경 후 (original): {original_nested_list}") print(f"외부 리스트 변경 후 (shallow): {shallow_copied_list}") original_nested_list[2].append(50) # 내부 중첩 리스트 변경: 서로 영향 있음 print(f"\n내부 리스트 변경 후 (original): {original_nested_list}") print(f"내부 리스트 변경 후 (shallow): {shallow_copied_list}")출력 결과:
Original ID: 140234567890123 Shallow Copied ID: 140234567890456 두 변수는 동일한 객체인가? False 원본 내부 리스트 ID: 140234567890789 복사본 내부 리스트 ID: 140234567890789 내부 리스트는 동일한 객체인가? True 외부 리스트 변경 후 (original): [1, 2, [3, 4], 5] 외부 리스트 변경 후 (shallow): [1, 2, [3, 4]] 내부 리스트 변경 후 (original): [1, 2, [3, 4, 50], 5] 내부 리스트 변경 후 (shallow): [1, 2, [3, 4, 50]]불변 객체와 얕은 복사
# 불변 객체가 포함된 경우의 예제 original = [1, "hello", (2, 3)] shallow = original.copy() print(f"문자열 객체 동일성: {original[1] is shallow[1]}") # True print(f"튜플 객체 동일성: {original[2] is shallow[2]}") # True # 불변 객체는 얕은 복사에서도 참조를 공유하지만, 변경할 수 없으므로 문제없음💡 알아두기: 문자열, 숫자, 튜플 등의 불변 객체는 얕은 복사에서 참조를 공유해도 변경할 수 없기 때문에 문제가 되지 않는다. 파이썬은 메모리 효율성을 위해 불변 객체를 공유하는 경우가 많다.
3. 깊은 복사 (Deep Copy)
원본 객체뿐만 아니라 그 내부에 있는 모든 중첩된 객체들까지 완전히 새로운 객체로 복사하는 방식이다.
원리와 특징
- 원리: 원본 객체와 그 안에 포함된 모든 하위 객체들을 재귀적으로 복사하여 완전히 독립적인 새로운 객체 트리를 생성한다.
- 사용 방법:
copy모듈의copy.deepcopy()함수를 사용해야 한다. - 결과: 원본과 복사본은 완전히 독립적이며, 어떤 변경도 서로에게 영향을 주지 않는다.
- 특징: 복사본의 어떤 부분(외부 객체든 내부 객체든)을 수정해도 원본은 영향을 받지 않는다.
🔍 성능 고려사항: 깊은 복사는 가장 안전하지만, 복잡한 객체 구조에서는 메모리 사용량과 처리 시간이 크게 증가할 수 있다. 따라서 정말 필요한 경우에만 사용하는 것이 좋다.
예제
import copy original_nested_list = [1, 2, [3, 4]] deep_copied_list = copy.deepcopy(original_nested_list) # 깊은 복사 print(f"Original ID: {id(original_nested_list)}") print(f"Deep Copied ID: {id(deep_copied_list)}") print(f"두 변수는 동일한 객체인가? {original_nested_list is deep_copied_list}") print(f"원본 내부 리스트 ID: {id(original_nested_list[2])}") print(f"복사본 내부 리스트 ID: {id(deep_copied_list[2])}") print(f"내부 리스트는 동일한 객체인가? {original_nested_list[2] is deep_copied_list[2]}") original_nested_list[2].append(50) # 내부 중첩 리스트 변경: 서로 영향 없음 print(f"\n내부 리스트 변경 후 (original): {original_nested_list}") print(f"내부 리스트 변경 후 (deep): {deep_copied_list}")출력 결과:
Original ID: 140234567890123 Deep Copied ID: 140234567890456 두 변수는 동일한 객체인가? False 원본 내부 리스트 ID: 140234567890789 복사본 내부 리스트 ID: 140234567891012 내부 리스트는 동일한 객체인가? False 내부 리스트 변경 후 (original): [1, 2, [3, 4, 50]] 내부 리스트 변경 후 (deep): [1, 2, [3, 4]]4. 실무에서의 선택 가이드
언제 어떤 복사를 사용할까?
상황 권장 방법 이유 단순한 1차원 리스트/딕셔너리 복사 얕은 복사 성능이 좋고 충분함 중첩된 가변 객체가 있고, 내부 객체도 독립적으로 수정해야 하는 경우 깊은 복사 완전한 독립성 보장 설정값이나 템플릿 객체를 기반으로 새 객체 생성 깊은 복사 원본 보호 단순히 같은 객체를 다른 이름으로 참조 참조 할당 메모리 효율적 복사 방법별 성능 비교
import copy import time # 복잡한 중첩 구조 생성 complex_data = [[i + j for j in range(100)] for i in range(100)] # 참조 할당 start = time.time() ref_assigned = complex_data ref_time = time.time() - start # 얕은 복사 start = time.time() shallow_copied = complex_data.copy() shallow_time = time.time() - start # 깊은 복사 start = time.time() deep_copied = copy.deepcopy(complex_data) deep_time = time.time() - start print(f"참조 할당: {ref_time:.6f}초") print(f"얕은 복사: {shallow_time:.6f}초") print(f"깊은 복사: {deep_time:.6f}초")📊 성능 팁: 대부분의 경우 얕은 복사로 충분하며, 깊은 복사는 정말 필요한 경우에만 사용하자. 특히 대용량 데이터를 다룰 때는 복사 방식의 선택이 성능에 큰 영향을 미칠 수 있다.
5. 결론 및 베스트 프랙티스
- 참조 할당은 단순히 이름만 추가하는 것이므로, 원본과 복사본이 항상 함께 변경된다.
- 얕은 복사는 새로운 껍데기를 만들지만, 그 안의 중첩된 가변 객체들은 여전히 원본과 공유한다. 중첩된 리스트나 딕셔너리를 다룰 때 주의해야 한다.
- 깊은 복사는 모든 것을 완전히 새로 만들어 원본과 복사본을 완벽하게 분리한다. 가장 안전한 복사 방법이지만, 객체 구조가 복잡할수록 메모리와 시간 소모가 커질 수 있으니 필요할 때만 사용하는 것이 좋다.
🎯 실무 권장사항
- 기본적으로는 얕은 복사를 사용하고, 문제가 생기면 깊은 복사를 고려하자.
- 불변 객체(문자열, 튜플, 숫자)만 포함된 구조라면 얕은 복사와 깊은 복사의 결과가 동일하므로 얕은 복사를 사용하자.
- API 응답이나 설정 데이터처럼 원본을 보호해야 하는 경우에는 깊은 복사를 사용하자.
- 디버깅할 때는
id()함수와is연산자를 활용해 객체의 동일성을 확인하자.
각 상황의 필요에 따라 어떤 방식의 복사가 필요한지 정확히 이해하고 사용하는 것이 파이썬 코드를 작성할 때 오류를 줄이고 효율성을 높이는 중요한 방법이다.
728x90반응형'언어·프레임워크 > Python' 카테고리의 다른 글
[Python] UV 치트 시트 (0) 2025.08.03 [Python] 윈도우에서 pyenv-win 삭제 방법 (1) 2025.08.03 [Python] 병행성(Concurrency)과 병렬성(Parallelism), 그리고 GIL에 대한 오해 (1) 2025.06.11 [Python] concurrent.futures: GIL, 동시성, 병렬성 완전 정복 (0) 2025.05.27 [Python] time 모듈: time.time(), time.perf_counter(), time.process_time() 설명 (0) 2025.05.12 다음글이 없습니다.이전글이 없습니다.댓글