본문 바로가기
Language & Tool/Python

🐍얕은 복사, 깊은 복사 비교

by 박수무당벌레 2025. 7. 1.

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] 원본 바뀜