0%

파이썬에서의 객체 복사

Python에서의 객체 복사

파이썬에서도 객체의 복사 개념이 존재한다.
복사 개념을 가지기 전에 먼저 객체에 대한 불변,가변의 속성을 보도록 하자.
가변적이라 함은 객체의 데이터를 수정할 수 있음을 의미하며, 불가변적은 객체의 데이터를 수정할 수 없다는 것을 의미한다.

class 구분
list 가변적(Mutable)
set 가변적(Mutable)
frozenset 불가변적(Immutable)
dict 가변적(Mutable)
tuple 불가변적(Immutable)
bool 불가변적(Immutable)
float 불가변적(Immutable)
str 불가변적(Immutable)

단순 객체 복사

1
2
3
a = [10, 20, 30]
a[0] = 1
print(a) # [1, 20, 30]

위는 아주 간단한 리스트를 선언하고 0번째 인덱스 값을 변경시켰다.
위 테이블에서 본 바와 같이 list의 경우 가변적인 객체이기에 값의 변경이 가능하다.
또한 id() 메서드를 통해 확인해도 값이 변하지 않음을 알 수 있다.

하지만 아래의 작은 예제를 보자.

1
2
3
t = (10, 20, 30)
t[0] = 1
print(t). # Error => TypeError: 'tuple' object does not support item assignment

위 코드를 수행시키면 TypeError 에러가 발생하게 된다.
그렇다면 값의 일부 수정이 아닌 값 자체를 변경(재 할당)하면 어떻게 될까?

1
2
3
4
t = (1, 2, 3)
print(t) # (1, 2, 3)
t = (4, 5, 6)
print(t) $ (4, 5, 6)

값이 완전히 바뀌며 id() 메서드를 통해 값을 출력해보면 값이 완전히 다른 것을 알 수 있다.

이와같이 값을 그냥 대입하는 것을 단순 객체 복사라고 한다.
즉 아래와 같은 것을 의미한다.

1
2
3
4
5
6
7
8
9
# case 3

t1 = (1, 2, 3)
t2 = t1

print('t1 = ', t1) # t1 = (1, 2, 3)
print('t2 = ', t2) # t2 = (1, 2, 3)
print('t1 id = ', id(t1)) # t1 id = 4572396712
print('t2 id = ', id(t2)) # t2 id = 4572396712

하나의 튜플(불가변)을 선언하고, 해당 튜플을 다른 변수에 넣어서 출력을 하니 위와 같은 결과가 나온다.
**즉 t2가 t1을 바라보고 있다 (좀 더 명확한 말로는 “같은 메모리 공간을 참조한다” 라고 표현한다.) **라고 할 수 있다.

만약 t1이 다른 튜플 값으로 재 할당을 한다면 t2도 당연히 t1과 동일하게 변할 것이다.
하지만 t2가 다른 튜플 값으로 재 할당을 할 경우 t1과 t2는 다른 값이 되어버린다.

이것은 불변 객체에서 확인하는 것보다 가변 객체에서 확인하는 것이 더 빠르다.
아래의 코드는 가변 객체 중 하나인 리스트를 통해서 위 예제를 재 구성 해보았다.

1
2
3
4
5
6
7
8
9
10
11
12
13
list1 = [1, 2, 3]
list2 = list1
list2[2] = 5
print('t1 = ', list1) # t1 = [1, 2, 5]
print('t2 = ', list2) # t2 = [1, 2, 5]
print('t1 id = ', id(list1)) # t1 id = 4453355272
print('t2 id = ', id(list2)) # t2 id = 4453355272

t2 = [3, 2, 1]
print('t1 = ', list1) # t1 = [1, 2, 5]
print('t2 = ', t2) # t1 = [3, 2, 1]
print('t1 id = ', id(list1)) # t1 id = 4453355272
print('t2 id = ', id(t2)) # t2 id = 4453355144

그런데 만약 위에서 list1의 값을 참조가 아닌 복사를 해서 사용해야 할 경우 어떻게 해야 할까?


얕은 복사(Shallow copy)

위에서의 문제점을 해결하기 위해서는 값을 참조하는 것이 아닌 복사를 해서 사용하는 개념이 필요할 것이다.
파이썬에서는 얕은 복사깊은 복사두 가지의 복사 개념이 있는데 먼저 얕은 복사를 알아보도록 하자.

아래의 코드는 위의 예제에서 list를 복사해서 사용예제로 바꾼 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
list1 = [1, 2, 3]
list2 = list1[:]
list2[2] = 5
print('t1 = ', list1) # t1 = [1, 2, 3]
print('t2 = ', list2) # t2 = [1, 2, 5]
print('t1 id = ', id(list1)) # t1 id = 4433817480
print('t2 id = ', id(list2)) # t2 id = 4433817352
t2 = [3, 2, 1]
print('t1 = ', list1) # t1 = [1, 2, 3]
print('t2 = ', t2) # t2 = [3, 2, 1]
print('t1 id = ', id(list1)) # t1 id = 4433817480
print('t2 id = ', id(t2)) # t2 id = 4432518024

출력 결과를 보면 알겠지만 list1의 값이 list2로 완전히 복사되었고, id의 참조 값 또한 각각으로 나눠진 것을 알 수 있다.
여기서는 복사를 위해 list 의 기능인 슬라이스를 통해 사용하였지만, 파이썬에서는 얕은 복사를 위해 copy라는 모듈을 제공하며, 사용은 다음과 같이 사용한다.

1
2
3
4
import copy
list1 = [1, 2, 3]
list2 = copy.copy(list1)
....

모든게 깔끔하게 된 것처럼(?) 보이나…
만약 아래의 예와 같은 변태코드가(?) 있다고 가정해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import copy
list1 = [1, ["abc", "bcd", "cde"], [2, 3, 4]]
list2 = copy.copy(list1)

# Test Case01
list1[0] = 3
print("list1 = ", list1) # list1 = [3, ['abc', 'bcd', 'cde'], [2, 3, 4]]
print("list2 = ", list2) # list2 = [1, ['abc', 'bcd', 'cde'], [2, 3, 4]]

# Test Case02
list1[1] = [5, 6, 7]
print("list1 = ", list1) # list1 = [3, ['abc', 'bcd', 'cde'], [5, 6, 7]]
print("list2 = ", list2) # list2 = [1, ['abc', 'bcd', 'cde'], [2, 3, 4]]

# Test Case03
list1[2].append(10)
print("list1 = ", list1) # list1 = [3, [5, 6, 7], [2, 3, 4, 10]]
print("list2 = ", list2) # list2 = [1, ['abc', 'bcd', 'cde'], [2, 3, 4, 10]]l

위 예제에서 Test case01, 02는 우리가 위에서 했던 것들과 별반 차이는 없다.
하지만 Test case03의 경우 리스트 내의 리스트에 값을 추가하는 것을 하였다.
이렇게 될 경우 값이 복사가 아닌 참조를 한 것과 같은 결과가 일어났다.
왜 이런일이 일어났는지는 조금만 깊게 생각해보면 알 수 있다.

먼저 list1의 데이터를 감싸는 list의 경우 값의 복사가 일어났다.
하지만 list 안의 list의 경우 가변적인 자료형이며, 해당 값은 복사가 아닌 참조가 일어난다.
이걸 아주 쉽게 돌려 이야기 하자면…
쇠고기 스테이크를 구웠는데 겉은 바삭하게 익었지만 속은 하나도 안 익은 상태라 할 수 있다. 이건 무슨 기적의 논리인가 -_-;
그렇다면 이런 경우 어떤 식으로 해결해야 할까?


깊은 복사(Deep copy)

이 문제를 해결하기 위해서는 깊은 복사를 사용해야 한다.
깊은 복사를 사용하게 되면 객체 내부의 값들 모두를 전부 복사하게 된다.

깊은 복사의 경우 겉의 객체 뿐만이 아닌 그 객체에 담긴 모든 것을 새롭게 복사(생성) 하게 되고, 처음에 생성한 객체와는 전혀 다른 객체가 생성되기 때문에 위의 문제는 자연스럽게 해결이 된다.

아래는 위의 코드에서 깊은 복사를 사용한 버전이다.

1
2
3
4
5
6
7
import copy
list1 = [1, ["abc", "bcd", "cde"], [2, 3, 4]]
list2 = copy.deepcopy(list1)

list1[2].append(10)
print("list1 = ", list1) # list1 = [3, [5, 6, 7], [2, 3, 4, 10]]
print("list2 = ", list2) # list2 = [1, ['abc', 'bcd', 'cde'], [2, 3, 4]]

위 코드에서 본 것과 같이 copy.deepcopy(list1) 이것이 바로 깊은 복사를 하는 것이다.