깊은 복사

최동혁·2022년 12월 6일
0

Python

목록 보기
3/10

파이썬의 깊은 복사

파이썬 깊은 복사 (copy.deepcopy)


2-1) 깊은 복사 copy.deepcopy

깊은 복사를 사용하기 위해서는 copy 모듈의 deepcopy 함수를 사용해야 한다.

깊은 복사는 리스트 내부 리스트, 딕셔너리 내부 리스트 등 내부에 있는 객체 모두 새롭게 만들어주는 작업을 한다.

모든 것 다 새롭게 복사.

그냥 독립적이 되어버림.

2-2) copy.deepcopy 깊은 복사 코드 예제

import copy                 # copy 모듈 불러오기
print('=' * 50)

arr1 = [1, 2, [99, 88, 77], 3]
arr2 = copy.deepcopy(arr1)      # copy 모듈 깊은 복사

print("1. 전체 출력")
print(f'arr1 : {arr1}, address : {hex(id(arr1))}')
print(f'arr2 : {arr2}, address : {hex(id(arr2))}')

print("\n2. 리스트에 새 key, value 추가")
arr1.append(0)
print('arr1.append(0)')
print(f'arr1 : {arr1}, address : {hex(id(arr1))}')
print(f'arr2 : {arr2}, address : {hex(id(arr2))}')

# 리스트 내부에 리스트 추가
print("\n3. 리스트 내부 리스트.")
print(f"arr1[2] : {arr1[2]}, address : {hex(id(arr1[2]))}")
print(f"arr2[2] : {arr2[2]}, address : {hex(id(arr2[2]))}")

print("\n4. 리스트 내부 리스트에 값 추가")
arr1[2].append(10)
print("arr1[2].append(10)")
print(f"arr1[2] : {arr1[2]}, address : {hex(id(arr1[2]))}")
print(f"arr2[2] : {arr2[2]}, address : {hex(id(arr2[2]))}")

print("\n5. 리스트 전체 다시 확인")
print(f'arr1 : {arr1}, address : {hex(id(arr1))}')
print(f'arr2 : {arr2}, address : {hex(id(arr2))}')

python deepcopy

python deepcopy

1 번 전체 출력을 보면arr1, arr2의 주소 값이 다른 것을 볼 수 있다.

3번의 리스트 내부 리스트도 보면 이전에 얕은 복사와 달리 arr1[2], arr2[2] 의 [99, 88, 77]인 리스트 내부 리스트도 주소 값이 다른 것을 볼 수 있다.

그래서 4번에서 리스트 내부 리스트인 arr1[2]에 값을 추가해도 arr2[2]에는 전혀 영향이 없는 것 을 알 수 있다.

2-3) slicing 깊은 복사 코드 예제

print('=' * 50)

arr1 = [1, 2, 3]
arr2 = arr1[:]     # copy 모듈 깊은 복사
print("1. 1차원 리스트")
print("1-1. 전체 출력")
print(f'arr1 : {arr1}, address : {hex(id(arr1))}')
print(f'arr2 : {arr2}, address : {hex(id(arr2))}')

print("\n1-2. 리스트에 새 key, value 추가")
arr1.append(0)
print('arr1.append(0)')
print(f'arr1 : {arr1}, address : {hex(id(arr1))}')
print(f'arr2 : {arr2}, address : {hex(id(arr2))}')

print('=' * 50)

arr1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
arr2 = [item[:] for item in arr1]
print("2. 2차원 리스트")
print("2-1. 전체 출력")
print(f'arr1 : {arr1}, address : {hex(id(arr1))}')
print(f'arr2 : {arr2}, address : {hex(id(arr2))}')

print("\n2-2. 리스트에 새 key, value 추가")
arr1[0].append(0)
print('arr1.append(0)')
print(f'arr1 : {arr1}, address : {hex(id(arr1))}')
print(f'arr2 : {arr2}, address : {hex(id(arr2))}')

위에 얕은 복사에서 슬라이싱을 이용했을 때에는 깊은 복사라고 하기에도, 얕은 복사라고 하기에도 애매하다고 했다.

그렇지만 얕은 복사였다.

하지만 1차원 리스트 내에 다른 리스트가 존재하지 않는다면, immutable 타입들만 들어가있다면, 이것 또한 깊은 복사라고 할 수 있다.

2차원 리스트도 마찬가지이다.

2차원 리스트 내의 있는 리스트들 안에 immutable 타입들로만 구성되어 있다면?

그렇다면 이것 또한 slicing을 통해 깊은 복사를 구현할 수 있다.

그리고 deepcopy는 slicing에 비해 속도가 엄청나게 느리다!

리스트의 깊은복사에는 deepcopy가 많게는 100여배정도 느린 속도를 보여준다. 

deepcopy가 느린 속도를 보이는 이유를 생각해보면

  1. slicing은 리스트의 요소 갯수만큼의 시간 복잡도를 가진다. copy모듈 안에서 여러 연산을 수행하는 것 보다 시간이 적게 소요될 수 있다.
  2. 모듈 import에 꽤 긴 시간이 소요될 수 있다.
  3. deepcopy 모듈은 객체의 모든 속성과 데이터를 복사해온다. 때문에 배열보다는 class객체나, dictionary같은 해쉬값을 복사해올때 이점이 있을 것 같다. 또한 코드를 뜯어보면 memo 속성을 넣을 수 있는데, 최적화하는데 사용할 수 있다고 한다.

deepcopy 모듈은 굉장히 느리다고 한다. 잠깐 검색만 해봐도 많이 느리다고 하는 결과를 볼 수 있다.

때문에 객체등을 복사할 때는 복사 메서드를 커스텀으로 만들어서 사용한다고 한다.

결론적으로 배열의 깊은 복사를 사용할때는 slicing을 이용하시는걸 추천한다.

결론

즉, 복사한 이후부터는 '독립적이다.' '둘이 쌩깠다.' '이젠 아무 사이도 아니다.'인 상태가 "깊은 복사"인 것이다.

profile
항상 성장하는 개발자 최동혁입니다.

0개의 댓글