[Python] Copy

kkiyou·2021년 6월 10일
0

Python

목록 보기
11/12
post-custom-banner

파이썬에서 선언한 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]


1. 단순복사

완전히 동일한 객체를 생성한다.

1.1. Assignment

=을 활용해서 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


2. Shallow copy

Shallow copy(얕은 복사)는 복사한 object는 새로운 메모리 영역에 할당하나, object 내의 값은 복사할 object의 메모리 주소값을 참조하여 가져온다. 즉 겉에 있는 것만 얇게 복사해온다고 해서 얕은 복사 방법이라고 불린다. 내부에 있는 값은 복사가 아니라 참조해 오는 것이다.


2.1. Slicing

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

리스트의 원소의 값을 바꾸는 것은 리스트를 바꾸는 것은 아니다. 반면 리스트 자체를 다른 것으로 바꾸는 것은 새로운 메모리 영역에 새로운 값을 넣고 그 메모리 주소값을 참조하도록 바꾸게 된다. 따라서 참조하는 주소값이 변경되므로 xy의 값이 서로 달라지는 것이다.


2.2. list.copy Method

slicing과 동일하다.

x = [[0, 1], [2, 3]] # 복합 객체(Compound object)
y = x.copy()

2.3. copy.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


3. Deep copy

Deep copy(깊은 복사)는 복사할 object의 모든 값을 새로운 메모리 영역에 할당한다.

3.1. copy.deepcopy()

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

참고자료 1

post-custom-banner

0개의 댓글