파이썬에서 선언한 object의 원소(Element)를 바꿀 수 있는 Mutable object는 list, tuple, dictionary 총 세 가지다. 다른 모든 것은 object를 재할당(Reallocation)한다. 즉 리스트 a = [0, 1, 2]
에서 a[0] = 2
로 원소를 바꾸면 a = [2, 1, 2]
로 a가 가리키는 list 클래스의 메모리 주소값은 동일하지만, a = [4, 4, 4]
는 object a
에 [4, 4, 4]
라는 값을 재할당하는 것이다. 따라서 메모리 주소값이 달라진다.
>>> a = [0, 1, 2] >>> print(id(a)) 4400033024 >>> a[0] = 2 >>> print(id(a)) 4400033024 >>> a = [4, 4, 4] >>> print(id(a)) 4400032768
한편, 아래와 같이 str object의 원소를 변경하려고 하면 Error
가 발생한다.
>>> st = "Hello, world!" >>> print(st[0]) H >>> st[0] = 'h' TypeError: 'str' object does not support item assignment >>> st = 'h' # 재할당 하는 것으로 원소를 변경하는 것과 다름. >>> print(st) 'h'
반면 list object는 원소를 변경할 수 있다.
>>> li = list(range(10)) >>> print(li) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> print(type(li)) <class 'list'> >>> li[0] = 'K' >>> print(li) ['K', 1, 2, 3, 4, 5, 6, 7, 8, 9]
완전히 동일한 객체를 생성한다.
=
을 활용해서 object를 복사하면 같은 메모리 주소값을 복사해온다. 따라서 메모리 주소값이 가리키는 데이터를 바꾸게 되므로, 어느 하나의 값이 변경되면 다른 하나의 값도 동시에 변경된다.
x = [[0, 1], [2, 3]] y = x for i in range(2): print(f"------{i}------") print(x[i] is y[i]) for j in range(2): print(x[i][j] is y[i][j])
------0------ True True True ------1------ True True True
print(x is y) print(id(x)) print(id(y))
True 4410730368 4410730368
y[0][0] = 'K' print(x) print(y)
[['K', 1], [2, 3]] [['K', 1], [2, 3]]
print(x[0][0] is y[0][0]) print(id(x[0][0])) print(id(y[0][0]))
True 4347756784 4347756784
y[0] = 'K' print(x) print(y)
['K', [2, 3]] ['K', [2, 3]]
print(x[0] is y[0]) print(id(x[0])) print(id(y[0]))
True 4347756784 4347756784
Shallow copy(얕은 복사)는 복사한 object는 새로운 메모리 영역에 할당하나, object 내의 값은 복사할 object의 메모리 주소값을 참조하여 가져온다. 즉 겉에 있는 것만 얇게 복사해온다고 해서 얕은 복사 방법이라고 불린다. 내부에 있는 값은 복사가 아니라 참조해 오는 것이다.
Slicing으로 object를 복사하면 원소의 값 자체를 복사해온다. 즉 새로운 메모리 영역에 값이 할당되게 되고, 서로 다른 주소값을 가지게 된다.
x = [0, 1] y = x[:] print(x is y) print(id(x)) print(id(y))
False 4409836928 4402950080
y[0] = 'K' print(x) print(y)
[0, 1] ['K', 1]
print(x[0] is y[0]) print(id(x[0])) print(id(y[0]))
False 4345788688 4347756784
복합 객체(Compound object)
그런데 다른 object를 포함하고 있는 object인 복합 객체(Compound object)의 경우, 복사한 object는 새로운 메모리 주소 영역에 할당되나, object 안에 있는 object(아래 예시에서 [0, 1]
과 [2, 3]
)는 주소값을 복사해온다.
x = [[0, 1], [2, 3]] # 복합 객체(Compound object) y = x[:] print(x is y) print(id(x)) print(id(y))
False 4404085760 4411077504
# object 내 object의 경우 `x`의 object 내 object의 메모리 주소값을 참조하여 가져온다. for i in range(2): print(f"------{i}------") print(x[i] is y[i]) for j in range(2): print(x[i][j] is y[i][j])
------0------ True True True ------1------ True True True
y[0][0] = 'K' # 복합객체(Compound object)의 원소 변경 print(x) print(y)
[['K', 1], [2, 3]] [['K', 1], [2, 3]]
print(x[0][0] is y[0][0]) print(id(x[0][0])) print(id(y[0][0]))
True 4347756784 4347756784
y[0] = 'K' # 복합객체(Compound object) 변경 print(x) print(y)
[['K', 1], [2, 3]] ['K', [2, 3]]
print(x[0] is y[0]) print(id(x[0])) print(id(y[0]))
False 4410998208 4347756784
그렇다면 y[0][0] = 'K'
일 때와 y[0] = 'K'
일의 차이는 무엇일까? 아래 예시를 보자.
>>> x = [[0, 1], [2,3]] >>> print(id(x[0])) 4411605504
>>> x[0][0] = 9 >>> print(id(x[0])) 4411605504
>>> x[0] = 9 >>> print(id(x[0])) 4345788976
리스트의 원소의 값을 바꾸는 것은 리스트를 바꾸는 것은 아니다. 반면 리스트 자체를 다른 것으로 바꾸는 것은 새로운 메모리 영역에 새로운 값을 넣고 그 메모리 주소값을 참조하도록 바꾸게 된다. 따라서 참조하는 주소값이 변경되므로 x
와 y
의 값이 서로 달라지는 것이다.
slicing과 동일하다.
x = [[0, 1], [2, 3]] # 복합 객체(Compound object) y = x.copy()
copy.copy(x)
copy module을 import하여 사용한다.
import copy x = [[0, 1], [2, 3]] # 복합 객체(Compound object) y = copy.copy(x) print(x is y) print(id(x)) print(id(y))
False 4405651648 4407787456
# object 내 object의 경우 `x`의 object 내 object의 메모리 주소값을 참조하여 가져온다. for i in range(2): print(f"------{i}------") print(x[i] is y[i]) for j in range(2): print(x[i][j] is y[i][j])
------0------ True True True ------1------ True True True
y[0][0] = 'K' # 복합객체(Compound object)의 원소 변경 print(x) print(y)
[['K', 1], [2, 3]] [['K', 1], [2, 3]]
print(x[0][0] is y[0][0]) print(id(x[0][0])) print(id(y[0][0]))
True 4347756784 4347756784
y[0] = 'K' # 복합객체(Compound object) 변경 print(x) print(y)
[['K', 1], [2, 3]] ['K', [2, 3]]
print(x[0] is y[0]) print(id(x[0])) print(id(y[0]))
False 4405650176 4347756784
Deep copy(깊은 복사)는 복사할 object의 모든 값을 새로운 메모리 영역에 할당한다.
copy.deepcopy(x[, 메모])
copy module을 import하여 사용한다.
import copy x = [[0, 1], [2, 3]] # 복합 객체(Compound object) y = copy.deepcopy(x) print(x is y) print(id(x)) print(id(y))
False 4402284224 4402283136
# `x[0][0]` for i in range(2): print(f"------{i}------") print(x[i] is y[i]) for j in range(2): print(x[i][j] is y[i][j])
------0------ False True True ------1------ False True True
x[0][0]
, x[0][1][1]
과 y[0][0]
, y[0][1][1]
의 값이 같다. 999
를 넣었을 때도 동일한 것을 보면 Object Interning으로 인한 것은 아니다. 따라서 copy는 Mutable한 object만 복사하는 것을 알 수 있다. 왜냐하면 list의 원소는 int이기 때문에 Immutable하며, 복사할 수 없다. 참고자료
>>> import copy >>> x = [[999, [1, 4]], [2, 3]] >>> y = copy.deepcopy(x) >>> print(id(x), id(y), x is y) 4414124480 4414127040 False >>> print(id(x[0]), id(y[0]), x[0] is y[0]) 4413367744 4414126080 False >>> print(id(x[0][0]), id(y[0][0]), x[0][0] is y[0][0]) 4399547152 4399547152 True >>> print(id(x[0][1]), id(y[0][1]), x[0][1] is y[0][1]) 4414119296 4414125632 False >>> print(id(x[0][1][1]), id(y[0][1][1]), x[0][1][1] is y[0][1][1]) 4345788816 4345788816 True > ```
y[0][0] = 'K' # 복합객체(Compound object)의 원소 변경 print(x) print(y)
[[0, 1], [2, 3]] [['K', 1], [2, 3]]
print(x[0][0] is y[0][0]) print(id(x[0][0])) print(id(y[0][0]))
False 4345788688 4347756784
y[0] = 'K' # 복합객체(Compound object) 변경 print(x) print(y)
[[0, 1], [2, 3]] ['K', [2, 3]]
print(x[0] is y[0]) print(id(x[0])) print(id(y[0]))
False 4410638848 4347756784