Shallow Copy와 Deep Copy
Python에서 assignment를 수행될 때,
- 오른쪽이 reference (즉 variable이 =연산자 우측에 있음)인 경우
- 기본적으로 Shallow Copy가 발생한다.
하지만 특정 경우에는 Deep Copy 가 필요한 경우가 있기 때문에 이 둘의 차이를 명확히 알고 있는 것이 중요하다.
이 둘의 차이는 다음 그림이 간략하게 설명해 준다.
Shallow copy가 일어났다면
- 왼쪽의 경우처럼 실제 데이터가 있는 메모리영역 Referenced Object가 공유되고
- 이를 참조하는 새로운 Cloned Object가 만들어진다.
때문에 실제로는 같은 Object를 가리키고 있기 때문에
- Original Object에서 수정이 일어나면
- 해당 수정사항이 Cloned Object를 통해 접근하는 경우에도 똑같이 반영이 된다.
Deep copy는
- copy가 일어나는 시점에 똑같은 값을 가지는 Object를 새로 생성하고
- 해당 새로 생성된 Object를 가리키게 한다.
때문에 이후로는 각각의 reference가 다른 memory 영역을 가리키므로 한쪽의 수정이 다른 쪽에는 반영이 되지 않는다.
일반적으로 매우 큰 메모리를 차지하고 있는 object나
dynamic programming 등에서 사용되는 object 들은 shallow copy가 유용하지만,
값만 똑같고 다른 메모리 영역에 새로 할당을 꼭 해야 하는 경우나
원본은 보존하고 원본에 대한 복사본에서 처리를 하는 경우 등에는
deep copy가 꼭 필요하다.
단순 Assignment : 같은 object를 가리킴
Python에서 assignment는
- 왼쪽 variable (or name, reference)이 오른쪽에 놓인 value(or Object)를 참조하도록 하는 것으로
- 오른쪽 expression의 최종값을 value로 가지는 Object를 왼쪽의 variable이 가리키도록(refer to) 할당(=해당 object를 참조).
2023.06.13 - [Programming] - [Python] Variable (and Object)
즉, variable(or reference)이 assignment에서 오른쪽 operand인 경우,
왼쪽 operand인 variable(or reference)도 같은 object를 참조하게 된다.
immutable object에 대한 variable 이 우측 operand인 경우.
다음 예제 코드는 오른쪽 operand인 variaible이 immutable
object를 참조하는 경우이며 d0
와 d1
가 가리키는 object들이 같은 ID를 가지게 됨(=같은 Object 참조)
# 단순 assignment.는 shallow copy!
d0 = (1,2,3)
d1 = d0
print(f'id of d0:{id(d0)}, id of d1:{id(d1)}')
결과는 다음과 같음.
id of d0:140488388961920, id of d1:140488388961920
d1 is d0 : True
d1 == d0 : True
tuple은 immutable.
주의할 것은 immutable
object의 copy constructor
를 assignment의 오른쪽 operand로 놓은 경우도 같은 object를 가리키게 된다는 점이다. (생성을 하는 것처럼 보이지만... 변경불가의 object를 굳이 다시 만들 필요가 없으므로)
d2 = tuple(d0)
print(f'id of d2:{id(d2)}')
print(f'd1 is d0 : {d1 is d0}')
print(f'd1 == d0 : {d1 == d0}')
결과는 다음과 같음.
id of d2:140488388961920
d1 is d0 : True
d1 == d0 : True
mutable object에 대한 variable 이 우측 operand인 경우.
Mutable
의 경우도 단순 assignment는 같은 memory 영역을 가리킴 (Variable은 단순 reference임을 잊지 말자).
# Muutable obj.는 shallow copy!
d0 = [1,2,3]
d1 = d0
print(f'id of d0:{id(d0)}, id of d1:{id(d1)}')
print(f'd1 is d0 : {d1 is d0}')
print(f'd1 == d0 : {d1 == d0}')
결과는 다음과 같음.
id of d0:140489189478208, id of d1:140489189478208
d1 is d0 : True
d1 == d0 : True
단, immutable
의 경우와 달리 mutable
object에 대한 variable을 이용한 copy constructor에서는 새로 memory할당이 된 object를 가리키게 된다.
d2 = list(d0)
print(f'id of d2:{id(d2)}')
print(f'd1 is d0 : {d2 is d0}')
print(f'd1 == d0 : {d2 == d0}')
결과
id of d2:140650393264576
d1 is d0 : False
d1 == d0 : True
[:]
를 이용한 경우
immutable
의 경우엔 단순히 assignment 한 것과 같은 결과 이지만, mutable
인 경우엔 다른 메모리 영역에 새로 할당 이 이루어진다.
주의할 것은 deep copy는 아니라는 점임.
immutable의 경우
d0 = ([1,2,3],1,2,3) #Immutable
d1 = d0[:]
print(f'id of d0:{id(d0)}, id of d1:{id(d1)}')
print(f'd1 is d0 : {d1 is d0}')
print(f'd1 == d0 : {d1 == d0}')
결과
id of d0:140488387426592, id of d1:140488387426592
d1 is d0 : True
d1 == d0 : True
mutable의 경우
d0 = [[1,2,3],1,2,3] # mutable
d1 = d0[:]
print(f'id of d0:{id(d0)}, id of d1:{id(d1)}')
print(f'd1 is d0 : {d1 is d0}')
print(f'd1 == d0 : {d1 == d0}')
결과
id of d0:140488387495104, id of d1:140488387053504
d1 is d0 : False
d1 == d0 : True
주의할 것은 item이 list
와 같은 mutable collection인 경우 item 들이 새로 만들어지는 것은 아니라는 점이다.
d0 = [[1,2,3],1,2,3]
d1 = d0[:]
d1[0][0] = 777
print(f'd0:{d0}\nd1:{d1}')
결과
d0:[[777, 2, 3], 1, 2, 3]
d1:[[777, 2, 3], 1, 2, 3]
앞서 본 것처럼 d0
와 d1
은 다른 메모리 영역(list
를 위해 할당된)을 가리키나,
해당 list
의 index 0인 item이 mutable
인 [1,2,3]
list이며 이는 d0
와 d1
이 공유하고 있다.
때문에 한쪽을 수정할 경우 다른 쪽에도 영향을 주어 777
로 바뀐 것을 볼 수 있음.
즉, shallow copy가 이루어진 것임.
만약 item이 immutable collection이라면?
immutable collection은 수정이 안되기 때문에 변경이 이루어지면 그냥 새로 생성하여 할당이 된다. 때문에 shallow copy이지만 마치 deep copy처럼 변경된 것으로 보일 수 있으나, 해당 item에 처리가 이루어질 때 아예 다른 object가 만들어져서 재할당된 것이지 앞서의 assignment에서 deep copy가 된 건 아님.
d0 = [(1,2,3),1,2,3]
d1 = d0[:] # 이 시점까지 shallow copy로 ,d0와 d1모두 같은 (1,2,3) object를 가르킴.
print (d0[0] is d1[0])
d1[0] += (4,5) # tuple에 대한 augmented assignment는 새로운 object를 생성하고 재할당임.
print (d0[0] is d1[0])
print(f'd0:{d0}\nd1:{d1}')
결과는 다음과 같음.
True
False
d0:[(1, 2, 3), 1, 2, 3]
d1:[(1, 2, 3, 4, 5), 1, 2, 3]
copy
메서드를 이용한 경우 (=shallow copy)
immutable object들에서는 copy
메소드가 지원되지 않으며, mutable object에서만 지원함.
기본적으로 새로운 메모리 영역을 할당하지만, item으로 있는 mutable
object들은 공유를 하기 때문에 shallow copy임.
위의 그림에서 왼쪽에 해당하며,bag0
에 대해 shallow copy를 수행한 bag1
경우처럼 item들은 새로 만들어지는 것이 아니다.
a = [[1,2,3],'test']
b = a.copy()
print(f'a is b:{a is b}')
b[0][0]=77
print(f'a is {a}\nb is {b}')
결과는 다음과 같음
a is b:False
a is [[77, 2, 3], 'test']
b is [[77, 2, 3], 'test']
deep copy : copy 모듈의 deepcopy() 함수
mutable
object가 item인 collection 객체들에 대해 deep copy를 하기 위해선 copy
모듈의 deepcopy
함수를 사용한다.
다음 예제 코드를 참고하라.
import copy
a = [[1,2,3],'test']
c = copy.deepcopy(a)
c[0][0] = 77
print(f'a is {a}\nb is {c}')
결과는 다음과 같다
a is [[1, 2, 3], 'test']
b is [[77, 2, 3], 'test']
literal의 경우
Python의 경우 memory의 효율적 사용을 위해서
literal 및 constant 등에 대해선 copy constructor
와 같이 동일한 memory 영역을 가리키는 형태로 처리함.
literal이란 : https://dsaint31.tistory.com/entry/Basic-Literal
다음 예제 코드를 참고하라.
a = 7
b = 7
print(f'a is b: {a is b}')
c = b
print(f'c is a: {c is a}')
결과는 다음과 같음.
a is b: True
c is a: True
memory의 효율적 사용을 위해서 literal
및 copy constructor
에선 shallow copy와 같이 같은 memory의 영역을 가리키게 됨.
물론 명시적으로 다음과 같이 값만 같은 tuple
을 따로 할당 하면 다른 memory 영역에 생성 됨
(이 부분이 literal
과 다름)
a = (1,2)
b = (1,2)
print( f'a is b :{a is b} / a == b : {a ==b}')
결과
a is b :False / a == b : True
'Programming' 카테고리의 다른 글
[Python] Iterable and Iterator, plus Generator (1) | 2023.06.07 |
---|---|
[Python] List Comprehension (0) | 2023.06.06 |
[Programming] Garbage Collection (GC) (0) | 2023.06.05 |
[Python] Interpreter and PVM (Python Virtual Machine) (1) | 2023.06.05 |
[Python] recursive call : Fibonacci Sequence (0) | 2023.05.24 |