파이문

파이썬 얕은 복사와 깊은 복사 본문

Python/Python

파이썬 얕은 복사와 깊은 복사

민Z 2017. 1. 9. 23:26

파이썬 얕은 복사와 깊은 복사

(Python shallow copy, deep copy)



파이썬에서 리스트나 대부분의 컬렉션을 복사하는 가장 손 쉬운 방법은 그 자료형 자체의 내장 생성자를 사용하는 것이다.

>>> l1 = [3, [55, 44]]
>>> l2 = list(l1) # l2 = l1[:]
>>> l2
[3, [55, 44]]
>>> l1 == l2
True
>>> l1 is l2
False

그러나 생성자나 [:] 키워드를 사용하면 얕은 복사(Shallow Copy)를 사용하게 된다. 사본은 원래 컨테이너에 들어 있던 동일 객체에 대한 참조로 채워지게 된다. 이 방식은 모든 항목이 불변형이면 메모리를 절약할 수 있지만 가변 항목이 들어있을 경우 예상치 못한 결과를 받게 될 수도 있다.

>>> l1 = [3, [66, 55, 44], (7, 8, 9)]
>>> l2 = list(l1)
>>> l1.append(100)
>>> l1[1].remove(55)
>>> l2[1] += [33, 22]
>>> l2[2] += (10, 11)

위와 같은 예제에서 과정은 다음처럼 진행된다.


우선 l1의 사본인 l2가 생성된다.

이 때 l1의 리스트 값은 얕은 복사로 생성되었지만 내부의 [66, 55, 44]와 (7, 8, 9)는 동일한 객체를 가르키게 된다.

l1에 100을 append하면 l1의 리스트 객체에만 100이 추가되지만 l1[1]에 remove를 하면 l2도 가르키는 l2의 첫번째 값, 리스트에 55가 제거된다.


이후 l2의 두번째 값인 튜플에 값을 추가하면 다음과 같이 변한다.

리스트와 다르게 튜플에 값을 추가하자 새로운 튜플 객체가 생성되었다. 그 이유는 튜플에 값을 추가하게 되면 새로운 튜플이 생성되기 때문이다(튜플은 불변형이다). id 함수를 통해 각각의 고유 주소 값을 확인해보자. l1[0], l2[0]이 동일하고 l1[1], l2[1]이 동일했다가, 튜플에 새로운 값을 추가하고 난 이후에 l1[1]과 l2[1]의 값이 달라지는 것을 확인할 수 있다.


자료형이 불변형인지 가변형인지에 따라서 전혀 예상하지 못한 값을 받게 될 수 있다. 그렇기에 조심해야 하는 부분이기도 하다.




객체의 깊은 복사와 얕은 복사


얕게 복사한다고 해서 늘 문제가 생기는 것은 아니지만, 내포된 객체의 참조를 공유하지 않도록 깊게 복사할 필요가 있는 경우가 있다. 이 때는 내장 모듈인 copy 모듈을 사용하면 된다.


깊은 복사의 경우는 deepcopy를, 얕은 복사인 경우는 copy 함수가 이를 지원한다.

아래는 deepcopy의 예제이다. 

>>> from copy import deepcopy
>>> lst1 = ['a','b',['ab','ba']]
>>> lst2 = deepcopy(lst1)
>>> lst2[2][1] = "d"
>>> lst2[0] = "c";
>>> print(lst2)
['c', 'b', ['ab', 'd']]
>>> print(lst1)
['a', 'b', ['ab', 'ba']]

lst2의 2번째 인덱스인 리스트 값을 변경하였지만 lst1의 값은 변경되지 않는 것을 확인할 수 있다.

반대로 deepcopy를 copy로만 바꾸어서 실행해보자. lst2의 리스트 값은 변경되지 않고 0번째 인덱스만 바뀐것을 확인할 수 있을 것이다.



deepcopy의 실행 결과

copy의 실행 결과

마지막으로 깊은 복사가 너무 깊이 복사하지 않도록, 복사하면 안 되는 리소스를 참조하지 않도록 조심해야 한다.


참고

전문가를 위한 파이썬

http://www.python-course.eu/deep_copy.php


같이 보면 좋을 게시글

http://stackoverflow.com/questions/17246693/what-exactly-is-the-difference-between-shallow-copy-deepcopy-and-normal-assignm

Comments