1. 복사의 필요성과 개념
복사란? 기존의 데이터를 변경하지 않고 동일한 구조나 내용을 가진 새 데이터를 만들어내는 작업
- list, dict, set 같은 가변 객체(mutable) 를 다룰 때 원본을 보존 + 새로운 객체로 작업을 수행해야 할 경우
→ 복사가 필요 - 얕은 복사와 깊은 복사 로 나눌 수 있으며, 중첩 구조를 복사할때 차이가 있음 (= 1차원 iterable 제외)
※ 왜 문자열, 튜플 같은 1차원 iterable 은 제외되는지
- 내부에 또 다른 객체를 포함하고 있지 않은, 중첩(nested) 되지 않은 단순한 구조
- 예시) 1차원 리스트는 대부분 불변형 자료로 구성돼 있음, 실질적 차이 없음
→ 각 요소는 불변(immutable) 이므로 얕은 복사 개념 적용 안 됨
→ 얕은 복사든 깊은 복사든 결과 동일
2. 얕은 복사(Shallow Copy) 와 깊은 복사(Deep Copy)
- 얕은 복사
- 상위 객체만 새롭게 생성하고, 그 내부에 포함된 하위 객체(ex. 리스트 안의 리스트) 는 참조만 복사 하는 방식
→ 겉 객체는 새로운 객체, 실제 내부의 중첩된 객체들은 원본과 동일한 주소 를 참조 (= 내부 공유)
→ 참조 공유로 성능은 빠르지만 안전도 낮음 - 원본 변경 시 복사본도 영향을 받음
- 반대로 내부 를 수정하면 복사된 객체와 원본 모두에 영향
- 내부에 mutable 객체 가 있을 때 참조 충돌 발생
- 깊은 복사
- 객체뿐만 아니라 그 객체가 포함하고 있는 모든 하위 객체까지 재귀적으로 복사 하는 방식
- 복사된 객체는 원본과 전혀 연결 X, 내부 구조까지 독립적 인 복제본
→ 전체 구조를 새로 복사 , 안전하지만 비용(성능, 메모리) 증가 - 다층 구조를 가진 복잡한 객체 안전하게 복제 + 원본 데이터와의 의도치 않은 연결을 방지
- 얕은 복사 vs 깊은 복사
| 얕은 복사 | 깊은 복사 | |
| 구조 | 상위 객체만 복사 | 전체 객체 계층을 모두 복사 |
| 참조 관계 | 외부는 새로 생성, 내부 객체는 원본과 공유 | 내부 객체까지 모두 새로 생성 |
| 영향 범위 | 내부 객체 변경 시 원본에 영향 | 내부 객체 변경 시 원본과 무관 |
| 속도 | 빠름 | 상대적으로 느림 |
| Python에서의 사용 | copy.copy(obj), 내부함수 .copy() 또는 list1[:], list(obj) 등 사용 | - copy.deepcopy(obj) 사용 - import copy 가 필요 |
| 예시 | 단순 1차원 리스트 나 구조가 얕은 경우, 얕은 복사로 충분 | - 복잡한 구조를 복제해야 할 때 (중첩된 리스트, 딕셔너리 등) - 외부 데이터 변경으로부터 원본을 보호해야 할 때 |
※ copy.copy(obj) 는 일반 함수(__copy__)를 호출하는 것, 내부함수 copy 는 C로 구현되어 직접 복사하는 것 이기 떄문에 내부함수가 약간 더 빠름 (대신 범용적으로 쓸 수 있는건 copy.copy())
- 주요 자료형 얕은 복사 vs 깊은 복사 비교 정리
| 얕은 복사 방법 | 깊은 복사 | 얕은 복사 특징 | 깊은 복사 특징 | |
| list | a[:] or list(a) or copy.copy(a) |
copy.deepcopy(a) | 최상위 리스트만 새로 생성, 내부 리스트는 참조 공유 | 내부 리스트까지 모두 복사 |
| dict | a.copy() or copy.copy(a) |
copy.deepcopy(a) | - key/value 쌍을 최상위 수준에서만 복사 - value가 mutable이면 참조 공유 |
value까지 재귀적으로 복사 |
| set | a.copy() or copy.copy(a) |
copy.deepcopy(a) | set 요소는 불변형만 가능, 내부 요소가 불변형이면 문제 없음 → 구조상 거의 깊은 복사처럼 동작 |
요소가 mutable 객체면 deepcopy 필요 |
| tuple | copy.copy(a) | copy.deepcopy(a) | tuple은 불변형이지만 내부에 mutable 객체가 있을 경우 참조 공유 | 내부의 mutable 객체까지 복사 |
| numpy | a.view() | copy.deepcopy(a) or np.copy(a) or a.copy() |
.view()는 메모리 공유 (얕은 복사) | 완전히 새로운 배열 생성 (데이터 변경 영향 없음) |
| pandas | df.copy(deep=False) | df.copy(deep=True) | DataFrame 구조만 새로 만들고, 내부 데이터는 참조 공유 | deep=True(기본값) 는 완전 복제 (열 객체, 데이터 모두 분리) → DataFrame 구조 + 내부 데이터 배열 |
※ pandas.DataFrame.copy(deep=True/False) (df.copy 원형)
3. 실제 사용 예시
- 중첩 리스트 를 사용하는 경우
- a 와 shallow 는 서로 다른 리스트이지만, a[0] 과 shallow[0] 는 같은 리스트를 참조
import copy
a = [[1, 2], [3, 4]]
# 얕은 복사
shallow = copy.copy(a) # 또는 shallow = a[:] (리스트에 한정)
# 깊은 복사
deep = copy.deepcopy(a)
# 원본 내부 객체 수정
a[0][0] = 99
# 결과 출력
print(a) # [[99, 2], [3, 4]] (원본)
print(shallow) # [[99, 2], [3, 4]]
print(deep) # [[1, 2], [3, 4]]
# 참조 비교 (같은 객체인지 확인)
print("a[0] is shallow[0]:", a[0] is shallow[0]) # True
print("a[0] is deep[0]:", a[0] is deep[0]) # False
- 1차원 리스트의 사용
- 내부 요소(int) 는 값 자체를 바꿀 수 없음, 새 값으로 교체
→ 숫자(불변형) 이기 때문에 복사본에서 바꿔도 원본엔 영향 없음
→ 참조를 공유 해도 값을 바꾸면 새로운 객체를 할당 하게됨 - ∴ 얕은 복사와 깊은 복사 결과가 같음
import copy
a = [1, 2, 3]
# 얕은 복사
shallow = a[:]
# 깊은 복사
deep = copy.deepcopy(a)
# b와 c의 첫 번째 원소만 수정
shallow[0] = 99
deep[1] = 88
print(a) # [1, 2, 3] → 전혀 영향 없음
print(shallow) # [99, 2, 3]
print(deep) # [1, 88, 3]
- 참조 전달과는 다름
- 함수의 인자는 참조를 전달 받음, 이때 내부 변경이 외부 변수에도 반영
→ route 리스트 자체가 복사 X, 같은 객체의 참조만 함수 인자로 넘어감 (= 원본 객체를 그대로 공유)
→ 그래서 함수 내부에서 append 하면 외부의 route도 그대로 바뀜 - 완전히 분리하고 싶다면 얕은 복사 사용
def add_item(lst):
lst.append(100)
nums = [1, 2, 3]
add_item(nums[:]) # 얕은 복사 전달
print(nums) # [1, 2, 3] ← 원본은 안 바뀜
add_item(nums) # 참조 전달
print(nums) # [1, 2, 3, 100] 원본 바뀜'Language & Tool > Python' 카테고리의 다른 글
| 🧵그래프를 표현하는 표현, 인접 리스트와 인접 행렬 (0) | 2025.07.04 |
|---|---|
| 🐍파이썬 함수 인자의 유연성 (0) | 2025.07.01 |
| 🐍반복 처리를 위한 itertools, more_itertools (1) | 2025.06.20 |
| 🌱논리 연산과 비트 연산(Python) (1) | 2025.06.18 |
| 🐍내부에서 작동하는 이중 밑줄 메서드 (+ 네임 맹글링, 단일 밑줄) (1) | 2025.06.17 |