얕은 복사(shallow copy) vs 깊은 복사(deep copy)


얕은 복사(shallow copy) vs 깊은 복사(deep copy)

객체의 복사는 얕은 복사와 깊은 복사로 나뉜다.

1. 단순 객체 복제

변수만 복사하면 변수가 가리키는 객체는 당연 동일할 것이다. 즉, 두 개의 변수 중 하나만 변경 되어도 나머지 하나도 동일하게 수정된다.

a = [1, 2, 3, 4]
b = a     # shallow copy
print(b)    # [1, 2, 3, 4]
b[2] = 100   # b의 item 변경
print(b)    # [1, 2, 100, 4]
print(a)    # [1, 2, 100, 4], a의 item도 수정됨!!

리스트를 만들어서 변수 a에 할당하였다.

그러면 a는 리스트 객체의 주소를 바라보는 변수가 된다.

그 다음 a를 b에 할당했다. 이러면 b가 a와 같은 객체의 주소를 바라보게 되는 것이다.

만약 a나 b를 이용해서 수정을 하면 어떻게 되는지 보자. 중간에 b의 2번째 인덱스에 접근하여 100으로 바꿔주었다.

이러면 a까지 b처럼 2번째 인덱스의 값이 100으로 바뀌게 된다.

이런 문제는 a와 b가 동일한 객체를 참조하기 떄문에 발생한다.

이때 주의할 점은 위처럼 복사된 참조 변수를 수정했을 때, 처음에 할당한 참조 변수의 값 역시 똑같이 수정되는 거는 변경가능(mutable) 객체일 때만 해당된다.

숫자나 문자열과 같이 불변의(immutable) 객체일 때는 이렇게 되지 않는다.

a = 10
b = a
print(b)    # 10 출력
b = "abc"
print(b)    # abc 출력
print(a)    # 10 출력

이 경우는 string이 immutable에 해당하기 때문에 이렇게 된다.

불변의 객체는 값이 바뀌지 않는 객체를 말한다. 따라서 참조변수를 수정한다는 것은 주소의 값(value)가 바뀌는 것이 아니라 그 변수에 새로운 객체가 할당되는 것을 의미한다.

2. 얕은 복사

단순복사와 구별되는 얕은 복사의 차이점은 복합객체(리스트)는 별도로 생성하지만 그 안에 들어가는 내용은 원래와 같은 객체라는 점이다.

import copy

a = [1, [1, 2, 3]]
b = copy.copy(a)    # shallow copy 발생
print(b)    # [1, [1, 2, 3]] 출력
b[0] = 100
print(b)    # [100, [1, 2, 3]] 출력,
print(a)    # [1, [1, 2, 3]] 출력, shallow copy 가 발생해 복사된 리스트는 별도의 객체이므로 item을 수정하면 복사본만 수정된다. (immutable 객체의 경우)

c = copy.copy(a)
c[1].append(4)    # 리스트의 두번째 item(내부리스트)에 4를 추가
print(c)    # [1, [1, 2, 3, 4]] 출력
print(a)    # [1, [1, 2, 3, 4]] 출력, a가 c와 똑같이 수정된 이유는 리스트의 item 내부의 객체는 동일한 객체이므로 mutable한 리스트를 수정할때는 둘다 값이 변경됨

리스트 내에 리스트가 있는 경우에 얕은 복사(b = copy.copy(a))가 이뤄지더라도 리스트 내의 내부 리스트까지 별도의 객체로 복사가 되는 것은 아니다.

위에서 b에서 첫번째 인덱스의 숫자를 변경했을 때 a가 바뀌지 않은 것은 그 요소가 immutable하기 때문이다.

그래서 요소가 수정이 되는 것이 아니라 그냥 다른 값으로 대체가 되는 것이다. 그래서 b에서 변경한 요소가 a에는 반영이 되어 있지 않은 것이다.

하지만 c를 보면 얘는 얘기가 다르다.

a를 복사해서 c를 만들고, c의 두번째 요소(리스트)에 새로운 값을 출력했다.

c의 내부리스트를 수정하게 되면 a의 내부 리스트 또한 바뀌는 것을 확인할 수 있는데, 그 이유는 a와 c의 내부리스트는 같은 객체를 참조하고 있기 때문이다.

b의 경우에도 같은 객체라고 할 수 있지만, 객체가 mutable하냐 immutable 하냐의 차이로 결과가 갈리게 된다.

mutable한 경우에는 값이 수정될 수 있지만, immutable한 경우에는 값이 수정되는 것이 아니라 아예 새로운 객체로 변경이 되는 것이다.

3. 깊은 복사

mutable한 내부객체(내부리스트)의 문제를 해결하기 위해서는 얕은 복사가 아니라 깊은 복사를 해야 한다.

얕은 복사가 복합객체(리스트)만 복사되고 그 안의 내용은 동일한 객체를 참조한다면, 깊은 복사에서는 복합객체를 새롭게 생성하고 그 안의 내용까지 재귀적으로 새롭게 생성하게 된다.

그래서 깊은 복사를 하게 되면 처음에 만든 객체와 복사된 객체가 아예 다른 객체이기 때문에 한쪽을 수정한다고 다른 쪽이 영향 받는 일은 없다.

import copy

a = [1, [1, 2, 3]]
b = copy.deepcopy(a)    # deep copy 실행
print(b)    # [1, [1, 2, 3]] 출력
b[0] = 100
b[1].append(4)
print(b)    # [100, [1, 2, 3, 4]] 출력
print(a)    # [1, [1, 2, 3]] 출력

정리하면 다음과 같다.

1.단순복제는 완전히 동일한 객체,

  1. 얕은복사(shallow copy)는 복합객체(껍데기)만 복사, 그 내용은 동일한 객체
  2. 깊은복사(deep copy)는 복합객체 복사 + 그 내용도 재귀적으로 복사