내가 그랬듯이 파이썬 사용자들이 자주 헷갈려하는 리스트 복사에 대한 내용을 정리했습니다.

파이썬의 리스트 복사는 4가지 경우가 있습니다.

  1. 데이터를 가리키는 메모리 주소를 복사하는 경우
  2. 데이터 자체를 복사하는 경우
  3. mutable 데이터의 메모리 주소를 복사하는 경우
  4. mutable 데이터 자체를 복사하는 경우

보기쉽게 코드로 먼저 확인해보겠습니다.

[1번 코드]

origin_data = [1, 2, 3, 4, 5]
copy_data = origin_data

# id()는 파이썬이 객체를 구별하기 위해 부여하는 코드값
# 동일 객체 여부를 판별할 때 사용
print(f'origin_data: {origin_data} / id: {id(origin_data)}')
print(f'copy_data: {copy_data} / id: {id(copy_data)}')

Result

결과를 보면 id값이 동일한 것을 보아 같은 데이터라고 볼 수 있습니다.

그리고

copy_data의 데이터를 변경해봤습니다.

copy_data[0] = 10
print(f'origin_data: {origin_data} / id: {id(origin_data)}')
print(f'copy_data: {copy_data} / id: {id(copy_data)}')

Result

origin_data의 데이터 역시 변경된 것을 확인할 수 있습니다.
이것으로 copy_data 변수는 origin_data의 메모리 주소를 복사했다는 것을 알 수 있습니다.

[2번 코드]

from copy import copy
origin_data = [1, 2, 3, 4, 5]
copy_data1 = origin_data[:]
copy_data2 = copy(origin_data)
# 아래는 위의 2가지 방식과 동일하게 복사되는 방법
# copy_data = list(origin_data)
# copy_data = [] + origin_data

print(f'origin_data: {origin_data} / id: {id(origin_data)}')
print(f'copy_data1: {copy_data1} / id: {id(copy_data1)}')
print(f'copy_data2: {copy_data2} / id: {id(copy_data2)}')

Result

id값이 각각 다르게 나온 것을 보면 [:]copy() 모두 원하던 데이터 복사가 수행된 것을 볼 수 있습니다.

copy_data1, 2의 데이터를 변경해봤습니다.

copy_data[0] = 10
print(f'origin_data: {origin_data} / id: {id(origin_data)}')
print(f'copy_data: {copy_data} / id: {id(copy_data)}')

Result

copy_data1, 2를 변경해도 origin_data가 변경되지 않는 것을 확인할 수 있습니다.

이게 완전히 복사가 된 것일까?

아닙니다, origin_data의 데이터가 mutable 객체인 경우는 다릅니다.

[3번 코드]

from copy import copy
origin_data = [[1, 2], [3, 4]]
copy_data1 = origin_data[:]
copy_data2 = copy(origin_data)

copy_data1[0][0] = 10
copy_data2[1] = 20
print(f'origin_data: {origin_data} / id: {id(origin_data)}')
print(f'copy_data1: {copy_data1} / id: {id(copy_data1)}')
print(f'copy_data2: {copy_data2} / id: {id(copy_data2)}')

Result

copy_data1의 0번 인덱스의 데이터는 [1, 2] 로 mutable 데이터입니다.
mutable 데이터 안에 있는 데이터를 변경하는 경우는 origin_data의 메모리 주소를 복사해서 갖고 있기 때문에 copy_data1, 2의 데이터가 모두 변하는 것을 확인할 수 있습니다.
3개 변수 모두 : [1, 2] → [10, 2]

하지만 mutable 데이터 전체를 다른 데이터 20 으로 변경한 경우는 정상적으로 copy_data2만 변경된 것을 확인할 수 있습니다.
copy_data2만 : [3, 4] → 20

이런 문제를 해결하기 위해서는 4번 방식을 사용해야 합니다.

[4번 코드]

from copy import deepcopy
origin_data = [[1, 2], [3, 4]]
copy_data1 = deepcopy(origin_data)

copy_data1[0][0] = 10
print(f'origin_data: {origin_data} / id: {id(origin_data)}')
print(f'copy_data1: {copy_data1} / id: {id(copy_data1)}')

Result

deepcopy() 함수를 사용한 복사만 mutable 데이터까지 완전하게 복사되는 것을 확인할 수 있습니다.

하지만 그렇다고 deepcopy()가 만능은 아닙니다.

이유는 복사하는 속도의 차이가 굉장히 크기 때문입니다.

속도가 가장 빠른 슬라이스 형식의 복사 [:] 에 비해서 deepcopy 복사는 30배 이상 느립니다.

그렇기 때문에 상황에 맞는 방식을 적절히 사용해야 합니다.

📌 Stack Overflow - 복사 속도에 대한 내용