본문 바로가기
Artificial Intelligence/60. Python

[PYTHON] copy와 deepcopy의 2가지 재귀적 처리 방식 차이와 성능 이슈 해결 방법

by Papa Martino V 2026. 3. 16.
728x90

copy와 deepcopy
copy와 deepcopy

 

파이썬에서 데이터를 다루다 보면 리스트나 딕셔너리 같은 가변 객체를 복제해야 할 상황이 반드시 생깁니다. 이때 단순히 대입 연산자(=)를 사용하는 것은 객체의 참조 주소만 복사하는 행위이므로 원본 데이터의 오염을 방지할 수 없습니다. 파이썬은 이를 위해 copy 모듈을 통해 얕은 복사(Shallow Copy)깊은 복사(Deep Copy)라는 두 가지 선택지를 제공합니다. 하지만 중첩된 구조를 가진 대규모 데이터를 다룰 때, 이 둘의 재귀적 처리 방식 차이와 그로 인한 성능 병목을 정확히 이해하지 못하면 심각한 메모리 낭비와 실행 속도 저하를 초래할 수 있습니다. 본 아티클에서는 파이썬 내부 메커니즘을 통해 이 문제를 심층적으로 분석하고 효율적인 해결책을 제시합니다.

1. 얕은 복사(copy.copy)의 선형적 메커니즘

얕은 복사는 새로운 복합 객체를 생성한 후, 원본 객체 안에 있는 요소들에 대한 '참조'만을 삽입합니다. 즉, 객체의 최상위 수준만 복제하고 내부의 중첩된 객체들은 원본과 공유하는 방식입니다.

  • 동작 방식: 새로운 컨테이너 생성 → 원본 요소들의 메모리 주소 복사 → 새 컨테이너에 삽입.
  • 장점: 복사 속도가 매우 빠르고 메모리 사용량이 적습니다.
  • 단점: 중첩된 가변 객체(리스트 안의 리스트 등)를 수정하면 원본과 복사본이 동시에 변합니다.

2. 깊은 복사(copy.deepcopy)의 재귀적 처리와 Memoization

깊은 복사는 원본 객체와 그 안에 포함된 모든 하위 객체들을 재귀적으로 탐색하여 완전히 새로운 독립적인 복사본을 만듭니다. 여기서 파이썬은 Memoization(메모이제이션) 기법을 사용하여 순환 참조(Circular Reference) 문제를 해결합니다.

표: copy vs deepcopy의 구조적/성능적 핵심 차이 비교

비교 항목 얕은 복사 (copy.copy) 깊은 복사 (copy.deepcopy)
재귀 탐색 여부 수행하지 않음 (최상위만) 모든 하위 계층까지 수행
독립성 수준 부분적 독립 (중첩 객체는 공유) 완전한 독립 (모든 객체 복제)
성능 (시간/메모리) 매우 빠름 / 효율적 매우 느림 / 자원 소모 큼
순환 참조 처리 해당 없음 Memo 딕셔너리로 추적 및 처리
주요 용도 단순 1차원 리스트/딕셔너리 복제 복잡한 트리/그래프 구조의 완전 복제

3. deepcopy의 치명적인 성능 이슈와 발생 원인

실무에서 deepcopy를 남발하면 프로그램이 눈에 띄게 느려지는 현상을 겪게 됩니다. 그 이유는 다음과 같습니다.

  1. 과도한 재귀 호출: 객체 트리가 깊을수록 스택 프레임이 쌓이고 오버헤드가 기하급수적으로 증가합니다.
  2. 객체 생성 비용: 모든 요소를 새로 생성(instantiate)해야 하므로 메모리 할당 속도가 병목이 됩니다.
  3. Memoization 오버헤드: 이미 복사된 객체인지 확인하기 위해 내부적으로 딕셔너리를 유지하고 조회하는 과정이 반복됩니다.

4. Sample Example: 복사 방식에 따른 데이터 변화와 성능 측정

아래 코드를 통해 두 방식의 데이터 독립성 차이와 실제 실행 속도를 직접 비교해 볼 수 있습니다.


import copy
import time

# 1. 구조적 차이 증명
original = [[1, 2, 3], [4, 5, 6]]
shallow = copy.copy(original)
deep = copy.deepcopy(original)

original[0][0] = 'X'

print(f"원본 수정 후 얕은 복사: {shallow}") # [['X', 2, 3], [4, 5, 6]] -> 오염됨
print(f"원본 수정 후 깊은 복사: {deep}")    # [[1, 2, 3], [4, 5, 6]]    -> 안전함

# 2. 성능 이슈 측정
large_data = [list(range(100)) for _ in range(5000)]

start = time.time()
copy.copy(large_data)
print(f"얕은 복사 소요 시간: {time.time() - start:.5f}초")

start = time.time()
copy.deepcopy(large_data)
print(f"깊은 복사 소요 시간: {time.time() - start:.5f}초")
# 결과: 데이터 규모가 커질수록 deepcopy는 수백 배 이상 느려질 수 있습니다.

5. 성능 저하를 방지하기 위한 3가지 해결 방법

복잡한 데이터를 안전하게 복제하면서도 성능을 챙길 수 있는 전문적인 방법들을 제안합니다.

  • 불변 객체(Tuple) 활용: 데이터 구조 내부에서 변하지 않는 부분은 튜플로 설계하십시오. 파이썬은 불변 객체를 복사할 때 실제 복제 대신 참조만 전달하므로 deepcopy 성능이 비약적으로 향상됩니다.
  • __deepcopy__ 메서드 오버라이딩: 클래스 설계 시 특정 속성만 복제하도록 로직을 커스텀화하여 불필요한 재귀 탐색을 줄일 수 있습니다.
  • 직렬화(Pickle) 트릭: 때로는 pickle.loads(pickle.dumps(obj)) 방식이 표준 deepcopy보다 빠르게 동작하는 경우가 있습니다. (단, 객체 종류에 따라 다름)

6. 결론: 올바른 복사 전략 수립하기

파이썬의 copydeepcopy는 데이터 무결성을 지키기 위한 필수 도구입니다. 하지만 이들의 동작 원리를 모른 채 사용하는 것은 성능이라는 큰 자산을 낭비하는 일입니다. 1차원 구조라면 copy나 슬라이싱([:])을 사용하고, 다차원 구조에서만 선택적으로 deepcopy를 사용하되, 앞서 언급한 최적화 기법을 병행하는 것이 가장 현명한 해결책입니다. 데이터 아키텍처를 설계할 때부터 가변성과 불변성의 조화를 고려한다면, 복사로 인한 성능 이슈를 사전에 차단할 수 있습니다.

콘텐츠 참조 및 기술 출처

  • Python Official Docs.
  • CPython Source Code: Lib/copy.py (Internal _deepcopy_atomic, _deepcopy_list 등)
  • Fluent Python by Luciano Ramalho: Chapter 8 - Object References, Mutability, and Recycling.
728x90