[파이썬] 얕은 복사(copy) vs 깊은 복사(deepcopy)

2022. 4. 28. 11:08몰랐던거/파이썬 문법

< 복사를 공부하기 이전에 파이썬의 mutable, imuutable 객체에 대해서 공부해보자 >


1. mutable, immutable 객체란??

파이썬은 객체를 두 종류로 구분

- mutable : 변경되는 객체 ( 객체 상태 변경 가능 ), ex) list, set, dictionary 등

- immutable : 변경되지 않는 객체 ( 객체 상태 변경 불가능 ) ex) int, float, tuple, str, bool 등

 

2. 이것이 복사에서 어떤 영향을 줄까?

> 파이썬에서는 immutable 객체의 값이 같은 경우에 변수에 상관없이 동일한 곳을 참조.  

> mutable 객체의 경우엔 각각의 객체를 모두 생성해서 참조.

immutable 객체의 주소와 mutable 객체의 주소 비교

- immutable

 x, y, z라는 변수는 각각 333이란 값을 가진다.  

 c, c++에서는 각 변수마다 메모리 주소 값이 다른데 파이썬은 다르다.  

 하나의 immutable 값에 여러 개의 참조가 붙는다.  

 즉, 변수의 값이 같으면 변수에 상관없이 동일한 곳을 참조.

=> immutable 객체들은 값이 바뀌면 참조만 바꾸면 되기 때문에 이런 식의 설계.

 

- mutable

 list1, list2, list3은 리스트 마다 모두 다른 메모리 주소 값을 가진다.

 즉, 리스트의 성분이 같아도 각각의 객체 모두 다른 메모리 주소 값 참조.

=> mutable한 리스트는 값이 자유롭게 바뀌기에 각각의 메모리 할당이 관리가 용이.

 

 

3. immutable한 객체의 실험

> 위에서 언급했던 immutable한 객체 : int, float, tuple, str, bool에 대해서 실험해보자.

 

3-1) int 

- 값이 달라질 때, 메모리 주소값이 변하는 것을 확인.

 

3-2) float

 

3-3) tuple, bool

tuple에 대한 실험 & bool에 대한 실험

- 위와 마찬가지로 값이 변하면 메모리 주소 값이 바뀜.

 

3-4) str

- 하지만 replace를 이용해 "333"을 "334"로 대체했는 경우엔 y와 z의 메모리 주소 값이 달라짐.

- 변경된 문자열이 항상 같은지 보고 같은 곳을 참조할지 판단하는 것이 어려워 항상 같은 곳을 참조 하지 않는 것.

 

4. mutable한 객체의 실험

1. 변수의 값이 같다 해도 메모리 주소 값이 다 다르게 나온다.

2. 변수의 내부 성부 값이 변하더라도 최초 참조 메모리 주소가 변경되지 않는다.

 

4-1) mutable 객체 안의 mutable vs immutable

- 리스트 내부에 immutable 요소 ex) 'a', 1, 2 등은 같은 값을 가지면 주소가 동일하다.

- 반면, mutable 요소 ex) [[1], [1]] 은 주소가 다르다.

- mutable 객체 안에 있는 각각의 요소도 결국은 어떤 값을 가리키는 참조 형태이기 때문이다.


 

< 복사에 대해 공부해보자 >


1. 파이썬 얕은 복사 ([:], copy, copy.copy)

- 얕은 복사라는 것은 변수를 복사했다고 생각했지만 실제로는 연결되어있는 것을 의미한다.

- 복사를 했지만 참조하는 주소 값이 같아서 같은 변수를 가리키고 있는 것이다.

파이썬 - 얕은 복사

 arr1 = [1,2,3]

 arr2 = arr1

이런 식으로 코드 작성 시 참조하는 곳이 같아진다. 그럼 append, remove 등의 요소가 변하는 함수를 사용 시 arr1, arr2가 같이 요소의 값이 변한다.

값을 변경하면 다른 변수에도 영향을 끼치도록 '참조'만 복사한 것을 얕은 복사라고 한다.

 

int, float과 같은 immutable한 객체는 값을 바꾸면 주소 값이 바뀌기 때문에 얕은 복사, 깊은 복사를 구분할 필요가 없다.

mutable한 객체 = list, set, dictionary와 같은 애들을 '얕은 복사', '깊은 복사' 를 구분하여 학습하여야 한다.

 

 

 

mutable 객체의 얕은 복사를 하는 방법은 총 4가지이다.

1-1) '=' 대입 연산자를 이용

 arr1 = [1,2,3]

 arr2 = arr2

위와 같은 코드이다.

둘 중 하나의 리스트에 값을 추가, 삭제하면 다른 리스트의 값도 변경된다.

즉, 참조하는 주소가 동일하다.

 

1-2) [:] 슬라이싱 이용

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

 arr2 = arr1[:]

이렇게 복사할 시 두개의 주소 값이 다르다.

 

 arr2.append(7)을 해보자

=> arr2 = [ 1, 2, 3, [4, 5, 6], 7]

이러한 결과가 나온다. 그러면 이 방법은 깊은 복사일까? 아니다

 

리스트 내부 리스트 부분을 보면 얕은 복사인 것을 알 수 있다.

 arr1[3] = [4, 5, 6]

 arr2[3] = [4, 5, 6]

 arr1[3].append(10)

=> arr1[3] = [4,5,6,10]

=> arr2[3] = [4,5,6,10]

이러한 결과가 나온다. 바깥 리스트는 다른 주소 값을 가리키고 있었지만 내부에 리스트가

같은 주소 값을 참조하고 있다.

 

1-3) copy 메서드 이용

위의 슬라이싱 [:] 과 동일한 결과가 나온다.

 arr2.copy = arr1.copy()

 

1-4) copy 모듈의 copy 함수 이용

import copy
dic1 = {'a': 'A', 'b': [1, 2, 3]}
dic2 = copy.copy(dic1)
dic2['c'] = 'C'

=> dic2 = {'a' : 'A', 'b' : [1,2,3], 'c' : 'C'}

=> dic1 = {'a' : 'A', 'b' : [1,2,3]} 

이러한 결과가 나오고 깊은 복사처럼 보인다.

하지만 이전과 비슷하게

dic1['b']와 dic2['b']의 주소값이 같아서 얕은 복사이다.

 

2. 파이썬 얕은 복사 (copy.deepcopy)

2-1) copy.deepcopy 이용

깊은 복사를 위해서 copy.deepcopy 이용

깊은 복사는 리스트 내부 리스트, 딕셔너리 내부 리스트 등 내부에 있는 객체 모두 새롭게 만들어주는 작업이다.

즉, 복사 후 독립적이다.

2-2) 반복문과 copy 메소드 이용

list1 = [[[1],[2],[3]], [[3],[4],[5]], [[7],[8],[9]]]
list2 = [ x.copy() for l in list1 for x in l]
list2[0].append(23)
print(list1)
print(list2)

위의 결과는 주소 값이 달라서 깊은 복사가 맞다.