파이썬 얕은 복사와 깊은 복사

Updated:

얕은 복사 $($copy, [:])

arr1 = [1, 2, 3, 4, 5]
arr2 = arr1

print(arr2) # [1, 2, 3, 4, 5]

arr1 = [1, 2, 3, 4, 5]이고 arr2 = arr1과 같이 대입연산자를 수행하면 arr2의 출력 결과는 [1, 2, 3, 4, 5]가 됩니다.

하지만 이는 얕은 복사로 arr2가 arr1과 같은 메모리 주소를 가리키고 있는 것입니다.

때문에 arr1의 값을 수정, 삽입, 삭제하게되면 arr2에도 변경사항이 동일하게 적용됩니다.

arr1.append(6)

print(arr2) # [1, 2, 3, 4, 5, 6]

이와 같이 참조만 복사한 것을 얕은 복사라고 합니다.

단, 얕은 복사 개념은 immutable한 객체들에는 적용되지 않습니다.

a = 3
b = a # 얕은 복사가 이루어짐. a와 같은 메모리 공간 참조

b = 4 # b는 4가 저장된 새로운 메모리 공간을 참조하게된다.
print(a) # 3

immutable 객체들은 값이 변경되면 무조건 참조가 변경되기 때문에 얕은 복사를 해서 b의 값을 변경하더라도 a의 값은 변경되지 않습니다.

이는 immutable 객체들은 값이 변경될 수 없기 때문에 메모리에 새로운 값을 할당해서 객체가 새로운 값의 주소공간을 참조하게 만들기 때문입니다.

따라서 얕은 복사, 깊은 복사에 대한 개념은 값의 변경이 가능한 mutable 객체로 한정됩니다. $($list, set, dictionary)

mutable 객체를 얕은 복사 하는 방법

’=’ 대입 연산자

mutable한 객체 $($list)

arr1 = [1, 2, 3, 4, 5]
arr2 = arr1

print(f"arr1 : {arr1}, {hex(id(arr1))}") # arr1 : [1, 2], 0x8fa0d77b8271
print(f"arr2 : {arr2}, {hex(id(arr2))}") # arr2 : [1, 2], 0x8fa0d77b8271

arr1.append(3)

print(f"arr1 : {arr1}, {hex(id(arr1))}") # arr1 : [1, 2, 3], 0x8fa0d77b8271
print(f"arr2 : {arr2}, {hex(id(arr2))}") # arr2 : [1, 2, 3], 0x8fa0d77b8271

arr1의 변경이 arr2에도 영향을 주고 참조하는 주소가 동일하다.

immutable한 객체 $($int)

num1 = 10
num2 = num1

print(f"num1 : {num1}, {hex(id(num1))}") # num1 : 10, 0x7fc0d77b8271
print(f"num2 : {num2}, {hex(id(num2))}") # num2 : 10, 0x7fc0d77b8271

num2 += 1
print(f"num1 : {num1}, {hex(id(num1))}") # num1 : 10, 0x7fc0d77b8271
print(f"num2 : {num2}, {hex(id(num2))}") # num2 : 11, 0x7fc0d77k8271

값을 변경했을 때 다른 주소를 가리킨다.

[:] 슬라이싱

전체 출력

arr1 = [1, 2, [3, 4]]
arr2 = arr1[:]

print(f'arr1 : {arr1}, {hex(id(arr1))}') # arr1 : [1, 2, [3, 4]], 0x4ff2993
print(f'arr2 : {arr2}, {hex(id(arr2))}') # arr2 : [1, 2, [3, 4]], 0x4ff2e9a

arr1과 arr2의 메모리 주소가 다르기 때문에 깊은 복사라고 생각할 수 있습니다.

리스트 끝에 값 추가

arr2.append(5)

print(f'arr1 : {arr1}, {hex(id(arr1))}') # arr1 : [1, 2, [3, 4]], 0x4ff2993
print(f'arr2 : {arr2}, {hex(id(arr2))}') # arr2 : [1, 2, [3, 4], 5], 0x4ff2e9a

arr2에 값을 추가해 보았더니 arr1에는 값의 변경이 일어나지 않았습니다.

주소값 또한 다르기 때문에 이쯤되면 깊은 복사인게 아닌가 싶습니다.

리스트 내부 리스트

print(f'arr1 : {arr1[2]}, {hex(id(arr1[2]))}')# arr1[2] :[3, 4] 0x4ff2993
print(f'arr2 : {arr2[2]}, {hex(id(arr2[2]))}')# arr2[2] :[3, 4] 0x4ff2993

리스트 안에 존재하는 내부 리스트인 [3, 4]를 출력해본 결과 같은 주소를 가리키고 있는것을 볼 수 있습니다.

이런 의미에서 얕은 복사라고 할 수 있습니다.

리스트 내부 리스트에 값 추가

arr1[2].append(6)

print(f'arr1 : {arr1[2]}, {hex(id(arr1[2]))}')# arr1[2] :[3, 4, 6] 0x4ff2993
print(f'arr2 : {arr2[2]}, {hex(id(arr2[2]))}')# arr2[2] :[3, 4, 6] 0x4ff2993

arr1 리스트 내부 리스트에 6을 추가해 보았더니 arr2 내부 리스트에도 6이 추가된 것을 확인할 수 있습니다.

주소값 마저 같으므로 얕은 복사입니다.

전체 출력

print(f'arr1 : {arr1}, {hex(id(arr1))}')# arr1 : [1, 2, [3, 4]], 0x4ff2993
print(f'arr2 : {arr2}, {hex(id(arr2))}')# arr2 : [1, 2, [3, 4], 5], 0x4ff2e9a

겉은 깊은 복사처럼 보이지만 내부는 얕은 복사로 이루어져 있습니다.

이러한 경우 얕은 복사로 구분합니다.

copy 메서드 사용

[:]와 동일한 결과가 나옵니다.

arr1 = [1, 2, [3, 4]]
arr2 = arr1.copy()

print(f'arr1 : {arr1}, {hex(id(arr1))}') # arr1 : [1, 2, [3, 4]], 0x4ff2993
print(f'arr2 : {arr2}, {hex(id(arr2))}') # arr2 : [1, 2, [3, 4]], 0x4ff2e9a

arr1[2].append(6)

print(f'arr1 : {arr1[2]}, {hex(id(arr1[2]))}')# arr1[2] :[3, 4, 6] 0x4ff2993
print(f'arr2 : {arr2[2]}, {hex(id(arr2[2]))}')# arr2[2] :[3, 4, 6] 0x4ff2993

깊은 복사 $($copy.deepcopy)

깊은 복사는 mutable 객체의 내부 객체를 모두 새롭게 만들어서 할당해줍니다.

copy 모듈의 deepcopy 함수를 사용하여 깊은 복사를 사용할 수 있습니다.

import copy

arr1 = [1, 2, [3, 4]]
arr2 = copy.deepcopy(arr1)

print(f'arr1 : {arr1}, {hex(id(arr1))}') # arr1 : [1, 2, [3, 4]], 0x4ff2993
print(f'arr2 : {arr2}, {hex(id(arr2))}') # arr2 : [1, 2, [3, 4]], 0x4ff2e9a

arr1[2].append(6)

print(f'arr1 : {arr1[2]}, {hex(id(arr1[2]))}')# arr1[2] :[3, 4, 6] 0x4ff2993
print(f'arr2 : {arr2[2]}, {hex(id(arr2[2]))}')# arr2[2] :[3, 4] 0x4ff29edf

참고 자료

  • https://blockdmask.tistory.com/576

Categories:

Updated:

Leave a comment