[python] 파이썬 얕은 복사, 깊은 복사 개념

업데이트:

파이썬 얕은 복사, 깊은 복사 개념

1. mutable, immutable한 객체

객체의 복사는 크게 얕은 복사(shallow copy)와 깊은 복사(deep copy)로 나누어 집니다. 그리고 객체의 종류에 따라 객체를 복사하는 방법에 차이가 납니다. 파이썬 객체는 크게 mutable 객체와 immutable 객체로 나뉩니다.
mutable 객체는 이름 그대로 변경 가능한 객체라는 의미이고 immutable 객체는 변경 불가능한 객체라는 의미입니다. 대표적인 mutable 객체에는 리스트가 있고 immutable 객체는 숫자, 문자가 있습니다.

x = 3
y = x

위 코드는 x라는 객체에 3이라는 값을 담고 y라는 객체는 x와 동일한 값을 가진다는 의미입니다.

>>> id(x)
4350900576
>>> id(y)
4350900576

id 함수를 사용하면 객체의 메모리 주소를 알 수 있습니다. 객체 x와 y 모두 동일한 메모리 주소를 가지고 있습니다.

>>> y = 7
>>> print(y)
7
>>> print(x)
3

y에 7을 할당한 후 y를 출력해보면 7을 확인할 수 있고, x값을 출력해보면 y값이 변경돤 것과 달리 x값은 여전히 3인 것을 볼 수 있습니다.

>>> id(x)
4350900576
>>> id(y)
4350900704

y에 7이라는 새로운 값을 할당한 후 다시 x와 y의 메모리 주소를 출력해보면 서로 다른 주소를 가지는 것을 볼 수 있습니다.

>>> a = [1,2,3,4]
>>> id(a)
4386238656
>>> b = a

>>> id(a)
4386238656
>>> id(b)
4386238656

이번엔 리스트에 대해 같은 테스트를 해보겠습니다. 객체 a, b는 동일한 메모리 주소를 가진다는 것을 확인할 수 있습니다.

>>> b.append(7)
>>> print(b)
[5, 2, 3, 4, 7]
>>> print(a)
[5, 2, 3, 4, 7]

이번에는 리스트 b에 새로운 값 7을 추가해보겠습니다. append 메소드를 이용해 리스트 b에 7이라는 값을 추가한 후 결과를 출력해보면 리스트 b에 7이 추가된 것을 볼 수 있습니다. 우리는 리스트 b에만 7을 추가했습니다. 그러나 실제로 리스트 a를 출력해보면 리스트 b와 동일하게 새로운 값 7이 추가된 것을 볼 수 있습니다. 이러한 문제를 해결하기 위해 앞으로 복사(copy) 개념을 배워보겠습니다.

2. 얕은 복사

import copy

a = [1,2,3,4,5]
b = copy.copy(a)

위 코드와 같이 copy 라이브러리의 copy 함수를 사용하면 얕은 복사를 할 수 있습니다.

>>> id(a)
4386232896
>>> id(b)
4386246592
>>> b[0] = 7
>>> print(b)
[7, 2, 3, 4, 5]

리스트 객체 b의 0번째 원소를 7로 변경해보겠습니다. b를 출력해보면 0번째 원소가 기존 1에서 7로 변경되어 있는 것을 볼 수 있습니다.

>>> print(a)
[1, 2, 3, 4, 5]

앞서 리스트 b의 원소만 수정했으므로 리스트 a의 원소는 변경없는 것을 볼 수 있습니다.

>>> id(a[0])
4350900512
>>> id(b[0])
4350900704

리스트 객체 a의 0번째 원소와 b의 0번쨰 원소의 주소를 확인해보면 서로 다른 메모리 주소를 가지고 있는 것을 볼 수 있습니다.

3. 깊은 복사

앞서 얕은 복사에 대해 알아보았습니다. 그러나 얕은 복사로는 mutable한 객체 내부에 mutable한 객체가 포함되는 경우, 예를 들어 리스트 속 원소로 리스트가 존재하는 경우에는 특정 개체에 작업을 하는 경우, 그 개체를 참조하는 객체 또한 함께 수정되는 문제점이 있습니다. 이를 해결하기 위한 방법이 깊은 복사 입니다.

import copy

a = [[1,2],[3,4]]
b = copy.deepcopy(a)

리스트 a의 구성 요소는 또 다른 리스트 입니다. 즉, mutable 한 객체 속에 mutable 한 구성 요소가 있는 경우입니다. 그리고 copy 라이브러리의 deepcopy 함수를 이용해 a를 깊은 복사해서 객체 b를 생성합니다.

>>> id(a)
4491120256
>>> id(b)
4492179392

그리고 각 객체의 메모리 주소를 확인해보면 서로 다른 메모리 주소라는 것을 알 수 있습니다.

>>> id(a[0])
4492531904
>>> id(b[0])
4492531264

이번에는 각 리스트의 0번째 리스트 요소의 메모리 주소를 확인해보겠습니다. 위 코드와 같이 메모리 주소가 서로 다른 것을 볼 수 있습니다.

>>> id(a[0][0])
4456941856
>>> id(b[0][0])
4456941856

각 리스트의 0번째 요소의 0번째 값의 메모리 주소 또한 서로 다른 것을 볼 수 있습니다.

>>> b[0][0] = 5
>>> print(b)
[[5, 2], [3, 4]]
>>> print(a)
[[1, 2], [3, 4]]

이번에는 리스트 b의 0번째 구성 요소 리스트의 0번째 값을 5로 수정해보겠습니다. 깊은 복사를 사용할 경우에는 b의 구성 요소를 변경했다고 하더라도 객체 a에는 영향을 주지 않는 다는 것을 알 수 있습니다.