[Python] copy 모듈 존재의 이유

Python의 'is' 커맨드를 설명하는 과정에서 Python의 대입은 레퍼런스 대입이라고 설명한 적이 있다. 그리고 이 레퍼런스 대입 시의 문제를 해결하기 위해 copy 모듈이 존재한다. 왜 copy 모듈이 필요한걸까.

레퍼런스 문제는 리스트에서도 여전히 발생한다.
>>> a = [1, 2, 3]
>>> b = a
>>> a
[1, 2, 3]
>>> b
[1, 2, 3]
>>> a[1] = -99
>>> a
[1, -99, 3]
>>> b
[1, -99, 3]
b가 a의 레퍼런스를 가져간 만큼 a가 바뀌면 b도 바뀐다. a 와 b 는 둘 다 동일한 메모리를 가리키고 있다.

이런 리스트 레퍼런스로 인한 문제는 리스트를 복사함으로써 해결 할 수 있다.
>>> a = [1, 2, 3]
>>> b = a[:]
>>> a[2] = -98
>>> b
[1, 2, 3]
이제 a와 b는 다른 메모리를 가리키고 있게 되었다.

여기서 사용한 [:] 문법은 copy 모듈의 copy를 이용해서 동일하게 동작하게 할 수 있다.
>>> a = [1, 2, 3]
>>> import copy
>>> b = copy.copy(a)
>>> b
[1, 2, 3]
>>> a[0] = 20
>>> b
[1, 2, 3]
copy.copy는 shallow copy라고 부른다. 어쨌든 결과적으로 리스트가 동일하게 복사되었다.

[:]문법을 이용해 복사하는 것과 다른 점은, copy.copy는 모든 타입의 변수를 다 복사한다는 점이다. [:]는 리스트에만 사용할 수 있으니까.

복사하는 방법을 알아냈지만 여전히 레퍼런스 문제가 발생하는 경우가 있다. 중첩(nested)된 리스트의 경우 리스트 안에 리스트가 존재하는 형태인데 이 경우를 시험해 보면...
>>> a = [1, 2, 3]
>>> b = [4, a, 5]
>>> b
[4, [1, 2, 3], 5]
>>> c = b[:]
>>> c
[4, [1, 2, 3], 5]
>>> a[0] = 100
>>> c
[4, [100, 2, 3], 5]
>>> b
[4, [100, 2, 3], 5]
b 안에서 a를 참조하고 있다. c는 b의 사본을 가져갔음에도 a에 영향을 받고 있다. 여기서 파악 가능한 건 [:]로 리스트를 복사하는 건 nested list까지는 적용이 되지 않는다는 점이다.

copy.copy의 경우도 동일하다.
>>> a = [1, 2, 3]
>>> b = [4, a, 5]
>>> c = copy.copy(b)
>>> c
[4, [1, 2, 3], 5]
>>> a[0] = 50
>>> c
[4, [50, 2, 3], 5]
shallow copy도 1차원적인 복사만을 한다는 것을 알 수 있다.

그렇다면 이런 문제를 회피하기 위해 [:]문법이나 copy.copy를 리스트 안의 모든 리스트를 돌아다니며 일일이 해 주면 해결할 수 있을 것이다.

... 그런데 너무 귀찮다!! ...

다행히도 해법을 제공해 주고 있으니 걱정할 건 없다.
>>> a = [1, 2, 3]
>>> b = [4, a, 5]
>>> import copy
>>> c = copy.deepcopy(b)
>>> c
[4, [1, 2, 3], 5]
>>> a[0] = -999
>>> c
[4, [1, 2, 3], 5]
>>> b
[4, [-999, 2, 3], 5]
copy.deepcopy는 이름 그대로 deep copy이다. 특히 nested list도 완벽하게 복사해 내는 등 거의 완벽한 복사를 자랑하는 모듈이다.

물론 deepcopy도 모든 타입에 이용이 가능하다.

개인적으론 고민하지 않고 공유메모리 형태로 사용하는 리스트의 데이터는 무조건 deepcopy로 복사해 버린다. 워낙 관련 문제를 많이 겪었더니...;;;

사족. 스크립트 언어에서 레퍼런스(포인터) 개념을 이용한다는 것은 굉장히 슬픈 일이다. 괜히 생각을 복잡하게 만들어야 하니까. 이 모든 것이 성능 때문에 쓰는 개념이라는 것을 이해해야 하기 때문에 더욱 슬퍼진다.

댓글

익명님의 메시지…
성능뿐만 아니라 구현의 이슈 때문이라도 레퍼런스(포인터) 개념은 꼭 필요합니다.

이 블로그의 인기 게시물

버전(Version)을 제대로 이해하기

소수점 제거 함수 삼총사 ceil(), floor(), round()