얕은 복사(shallow copy) vs 깊은 복사(deep copy)
January 20, 2016

이번에는 객체의 복사에 대해서 알아보려고 합니다. 객체의 복사는 크게 얕은 복사(shallow copy)와 깊은 복사(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도 수정됨!!

리스트를 만들어(파이썬에서 대괄호’[ ]‘는 list) 1 에서 4 까지 네개의 숫자를 넣고 a 에 할당했습니다. 그러면 a 는 리스트 객체의 주소를 바라보는 변수가 되는 것이죠. 그런뒤 a 를 b 에 할당해 주었습니다. 그러면 b 는 a 와 같은 객체의 주소를 바라보게 됩니다.

문제는 a 또는 b 를 수정하게 되면 문제가 발생합니다. 위에서 처럼 b 의 세번째(2) 값을 3 에서 100 으로 바꿔주게 되면, 당연히 b 는 [1, 2, 100, 4] 처럼 출력됩니다. 그런데 변경되지 않을 거라고 예상했던 a 또한 b 와 같이 [1, 2, 100, 4] 로 수정되어버렸습니다. 이는 a 와 b 가 동일한 객체를 참조하기 때문에 발생하는 문제입니다.

한가지 주의할 점은 위의 경우처럼 복사된 참조 변수를 수정했을때, 처음에 할당한 참조 변수의 값 역시 똑같이 수정되는 것은 리스트와 같은 변경가능(mutable) 객체일 때만 해당한다는 것입니다. 숫자나 문자열과 같은 불변의(immutable) 객체일때는 위의 경우가 해당되지 않습니다.

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

위와 같은 결과가 나타나는 이유는, 불변의 객체이기 때문입니다. 말장난 하는것 같아 보이지만, 불변의 객체란 값이 바뀌지 않는 객체를 뜻하죠. 그렇기 때문에 참조변수를 수정한다는 것은 같은 주소의 값(value)이 바뀌는 것이 아니라 그 변수에 새로운 객체가 할당되는 것을 뜻합니다.

2. 얕은 복사(shallow copy)

얕은 복사의 경우는 어떨까요? 단순 복사와 어떤 차이가 있을까요? 단순 복제와 얕은 복사의 차이점은 복합객체(리스트)는 별도로 생성하지만 그 안에 들어가는 내용은 원래와 같은 객체 객체라는 점입니다. 예제로 살펴보면 아래와 같습니다.

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 하기 때문입니다. immutable 하다는 것은, 요소가 수정되는 것이 아니라 그저 다른 값으로 대체된다고 볼 수 있죠. 그렇기 때문에 b 에서 변경된 요소가 a 에는 반영되어 있지 않은 것이죠.(두 리스트는 다른 객체이므로..)

그러나 c 의 경우는 좀 다릅니다. a 를 복사하여(c = copy.copy(a)) c 를 만듭니다. 그리고 c 의 두번째 요소(리스트)에 새로운 값을 추가합니다. 출력해보면 a 와 b 의 경우처럼 c 에 값을 수정했을때 a 는 수정되지 않을거라 기대되지만, 실제는 그렇지 않습니다. c 의 내부리스트를 수정하게 되면 a 의 내부리스트 또한 바뀌게 되는데요. 그 이유는 a 와 c 의 내부리스트는 같은 객체를 참조하기 때문입니다. b 의 경우에도 같은 객체라 말할 수도 있지만, 이 둘의 중요한 차이는 그 객체가 mutable 하냐 immutable 하냐의 차이입니다. mutable 한 경우에는 값이 수정될수 있지만, immutable 한 경우에는 값이 수정되는 것이 아니라 아예 새로운 객체로 변경되는 것이죠. 그래서 위와 같은 차이가 나타나게 되는 것입니다.

3. 깊은 복사(deep copy)

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

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

그래서 깊은 복사를 하게 되면, 처음에 만들었던 객체와 복사된 객체가 전혀 달라지기 때문에 어느 한쪽을 수정한다고 해서 다른 한쪽이 영향 받는 일은 없게되겠죠.

파이썬에서는 copy 모듈의 deepcopy()라는 메서드를 통해 깊은 복사를 손쉽게 구현할 수 있습니다.

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. 단순복제는 완전히 동일한 객체,
  2. 얕은복사(shallow copy)는 복합객체(껍데기)만 복사, 그 내용은 동일한 객체
  3. 깊은복사(deep copy)는 복합객체 복사 + 그 내용도 재귀적으로 복사