Python 리스트 복사 2가지 비교(얕은 복사, 깊은 복사)

Python에서 리스트 객체를 복사하는 방식에는 얕은 복사(shallow copy)와 깊은 복사(deep copy)가 있습니다. Python 리스트 복사 비교로 두 방식의 차이점을 살펴보겠습니다.

Python 리스트 복사 비교

리스트를 복사하는 방식에 얕은 복사와 깊은 복사란 무엇인지, 어떤 차이점이 있는지 정확히 이해해야 합니다. 그렇지 않으면 분명 나는 제대로 리스트를 다루었다고 생각했는데, 어느 순간 의도치 않은 값의 변화가 생길 수 있습니다. 코드와 함께 얕은 복사와 깊은 복사의 차이점을 확인해 보도록 하겠습니다.

Python 리스트 얕은 복사(shallow copy)

원본 리스트와 동일한 요소들을 가지는 새로운 리스트를 만들어 내지만, 리스트 내의 요소들의 참조는 원본과 동일한 참조를 갖게 됩니다.

우선 얕은 복사 방법을 알아보도록 하겠습니다. 슬라이싱 방식과 list.copy() 메서드를 사용하는 방법이 있습니다.

a = [1, 2, 3]
b = [4, 5, a]
c = b.copy()
d = b[:]
print("b:", b, id(b))
print("c:", c, id(c))
print("d:", d, id(d))
Python

위의 코드를 실행해 보았습니다. 라인별로 살펴보겠습니다.

  • 라인 1: 리스트 [1, 2, 3]을 변수 a에 할당했습니다.
  • 라인 2: 리스트 [4, 5, a]를 변수 b에 할당했습니다.
  • 라인 3: 리스트 b를 list.copy() 메서드를 이용해 새로운 변수 c로 얕은 복사(shallow copy)합니다.
  • 라인 4: 리스트 b를 슬라이싱을 이용해 새로운 변수 d로 얕은 복사합니다.
  • 라인 5, 6, 7: b의 메모리 상 주소와 c, d의 메모리 상 주소는 다른 것을 확인할 수 있습니다. 하지만 리스트의 요소는 모두 동일하게 복사가 된 것을 알 수 있습니다.
그림 1. Python 리스트 복사: 얕은 복사(shallow copy) 방식 2가지
그림 1. Python 리스트 복사: 얕은 복사(shallow copy) 방식 2가지

여기에서 재미있는 시도를 해 보도록 하겠습니다. 위에서 확인했듯이 b, c, d는 모두 다른 주소 값을 갖는 객체임을 알 수 있습니다. 이번에는 리스트 b, c, d에 들어있는 a가 동일한 참조를 갖는지 확인해 보겠습니다.

b[2] is c[2]
b[2] is d[2]
id(a)
id(b[2])
id(c[2])
id(d[2])
Python

아래의 출력 결과를 통해서 b[2]와 c[2]가 동일한 참조를 가지고 있음을 확인할 수 있습니다. a의 주소와 b, c, d의 3번째 요소가 동일한 주소 값을 갖는 것을 볼 수 있습니다.

그림 2. 얕은 복사 결과 모두 리스트 a의 동일한 참조값을 가짐
그림 2. 얕은 복사 결과 모두 리스트 a의 동일한 참조값을 가짐

이번엔 a의 3번째 항목의 값을 9로 변경해 보겠습니다.

a[2] = 9
a
b
c
d
Python

분명히 코드 상으로 리스트 a의 3번째 항목 3값만 9 값으로 변경해 주었는데, 아래 그림과 같이 b, c, d의 3번째 값인 리스트 a의 3번째 항목이 9로 모두 변경되었습니다.

그림 3. 얕은 복사가 되었으므로 a의 요소 변경시 모두 같은 값을 가리킴
그림 3. 얕은 복사가 되었으므로 a의 요소 변경시 모두 같은 값을 가리킴

이번엔 복사된 리스트 c의 값을 변경해 보겠습니다.

c[2][2] = 88
a
b
c
d
Python

c의 3번째 항목인 리스트 a의 3번째 항목 값을 88로 변경한 결과, a, b, c, d의 해당 요소가 88로 모두 변경되었습니다.

그림 4. 얕은 복사가 되었으므로 c의 요소 변경시 모두 같은 값을 가리킴
그림 4. 얕은 복사가 되었으므로 c의 요소 변경시 모두 같은 값을 가리킴

즉, 이와 같이 얕은 복사는 참조가 그대로 따라가는 형태의 복사이므로 복사 이후에 값을 변경하는 경우에 참조되는 값이 함께 변경된다는 점을 유의해야 합니다. 때로는 이와 같은 참조를 유지해서 변경된 값을 공유해야 하는 경우에는 얕은 참조 방식으로 리스트를 복사하면 됩니다.

Python 리스트 깊은 복사(deep copy)

이번엔 얕은 복사와는 달리 깊은 복사(deep copy)입니다. 깊은 복사는 참조의 모든 연결 고리가 끊어지고 모두 새로운 객체를 생성하고 해당 값들을 모두 새로 채워넣는 방식입니다. 따라서 깊은 복사 이후의 값 변경은 원본의 변경을 초래하지 않습니다.

from copy import deepcopy

a = [1, 2, 3]
b = [4, 5, a]
c = deepcopy(b)
print("b:", b, id(b))
print("c:", c, id(c))
Python

앞에서의 shallow copy나 deep copy나 결과는 크게 달라 보이지 않습니다.

그림 5. Python 리스트 복사: 깊은 복사(deep copy)
그림 5. Python 리스트 복사: 깊은 복사(deep copy)

하지만 이제부터는 다르다는 것을 보게 되실 것입니다.

a is b[2]
b[2] is c[2]
id(a)
id(b[2])
id(c[2])
Python

리스트 b는 a의 참조를 가지고 있기 때문에 동일한 주소를 갖지만, c[2]의 참조값과는 다른 것을 확인할 수 있습니다. 라인 2의 실행 결과도 b[2]와 c[2]의 참조값이 다른 것을 확인하게 해 줍니다.

그림 6. 깊은 복사를 했으므로 b와 c의 3번째 항목의 참조되는 주소가 다름
그림 6. 깊은 복사를 했으므로 b와 c의 3번째 항목의 참조되는 주소가 다름

얕은 복사에서 시도했던 것과 동일하게 리스트 a의 3번째 요소를 9로 변경해 줍니다.

a[2] = 9
a
b
c
Python

리스트 b의 3번째 요소인 리스트 a의 3번째 항목이 9로 변경된 것을 확인할 수 있습니다. 하지만 리스트 c의 값은 변화하지 않았습니다. 참조가 다르기 때문이지요.

그림 7. 리스트 a를 변경했을 때 b의 참조는 동일하므로 같은 값을 가리킴, c는 참조가 다르므로 깊은 복사했을 때의 값을 가짐
그림 7. 리스트 a를 변경했을 때 b의 참조는 동일하므로 같은 값을 가리킴, c는 참조가 다르므로 깊은 복사했을 때의 값을 가짐

이번에도 얕은 복사에서 시도했던 것과 동일하게 c[2][2] 값을 88로 변경해 보겠습니다.

c[2][2] = 88
a
b
c
Python

리스트 c의 값만 변경되었고, 리스트 a와 b의 해당 항목들은 달라지지 않은 것을 볼 수 있습니다.

그림 8. 리스트 c[2][2]를 변경했을 때 c의 값만 변경됨. a, b의 참조와 다름.
그림 8. 리스트 c[2][2]를 변경했을 때 c의 값만 변경됨. a, b의 참조와 다름.

정리

리스트를 복사하면서 리스트 내의 참조를 유지하고 싶다면 shallow copy(얕은 복사)를 사용하면 됩니다.

한편 리스트를 복사하면서 원본은 그대로 보존하기 원하면 deep copy(깊은 복사)를 사용하면 됩니다.

관련 자료

Python 문서 중 Data Structures, Built-in Functions를 참고했습니다.

같이 읽으면 좋은 글

Leave a Comment