[Python] 얕은복사와 깊은복사

Dunno·2021년 7월 11일
0

Python

목록 보기
1/3

파이썬의 객체를 다룰 때 무작정 복사를 하다 보면 원본 객체의 데이터가 변경되어 원치 않은 오류가 발생할 수 있다.
얕은복사(Shallow Copy)와 깊은복사(Deep Copy)는 객체를 복사하는 방법을 의미하고 이를 이해하기 위해선 mutable객체와 immutable객체에 대한 이해가 먼저 필요하다.

mutable객체와 immutable객체

  • C 등 다른 언어에서 변수는 각각의 자체적인 저장공간을 갖는다.
  • 하지만 파이썬에서 변수는 자체적인 저장공간을 갖는게 아닌 자신에게 대입된 객체를 가리키는 일종의 포인터 같은 존재라고 볼 수 있다.
# c에서의 변수 저장
int a = 1; # a라는 저장공간에 1을 저장한다.
# python에서의 변수 저장
a = 1 
# a와 1은 별개의 존재로, a는 1이라는 객체를 가리키고 있을 뿐
# a에 정수 1이 할당된 것이 아니다.

🎈 mutable객체

  • 리스트(list), 딕셔너리(dictionary), Numpy 배열(ndarray) 등
  • 리스트와 같은 mutable한 객체는 특성 대로 값이 바뀔수 있기 때문에 복사한 리스트의 값을 바꾸면 원본 리스트의 값 역시 바뀐다.
  • 정확하게는 같은 원본 리스트와 복사한 리스트 둘 다 같은 리스트 객체의 주소를 참조하므로 mutable한 리스트의 변화에 두 변수 모두 영향을 받는다.
# ex) 리스트에 대한 변환
a = [1, 2, 3, 4]
id(a)
>>> 4393775432

a[0] = 6
a
>>> [6, 2, 3, 4]

id(a)
>>> 4393775432 #id값 그대로

🎈 immutable객체

  • 숫자(int, float, bool), 문자열(string), 튜플(tuple) 등
  • 숫자나 문자열 같은 immutable객체는 값이 바뀔 수 없기 때문에 인덱싱을 통해서 값을 바꾸려 해도 값이 바뀌지 않고 Error가 발생한다.
  • 또한 문자열 'abc'가 저장된 변수 s에 다른 문자열 'def'를 저장하면 변수 s에 'abc' 대신 'def'가 저장되는 것이 아닌 'abc'를 가리키던 변수 s가 'def'를 가리키는 변수 s가 되는 것이다.
  • 이 때 'abc'를 가리키는 변수 s와 'def'를 가리키는 변수 s는 서로 다른 주소값을 갖는다.
# ex) 문자열에 대한 변환
s= "abc"
s
>>> 'abc'

id(s)
>>> 4387454680

s[0]
>>> 'a'

s[0] = 's'
>>> Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

s = 'def' 
# s에 다른 값을 재할당하는 것으로 id가 변경된다. mutable과 immutable과는 다른 개념
s
>>> 'def'

id(s)
>>> 4388970768 # id값이 변한다.

**결론 : 파이썬의 변수는 값을 저장하는 것이 아닌 객체의 주소를 참조하는 c의 포인터와 같은 동작을 한다.

얕은복사와 깊은복사

0.예외

🧨 배정구문 (b = a)

  • b=a의 형태로 복사를 하는 것은 얕은 복사나 깊은 복사의 개념이 아닌 배정 구문이라고 볼 수 있다.
  • 배정 구문은 복사본을 만드는 것이 아니라 타겟과 오브젝트를 엮는다는 개념에 가깝다.
a = [1, 2, 3, [4, 5]]
b = a
print(id(a), id(b))
>>> 
2265003288712 2265003288712

b[0] = 6
print(a, b)
print(id(a), id(b))
>>> 
[6, 2, 3, [4, 5]], [6, 2, 3, [4, 5]]
2265003288712 2265003288712

1. 얕은복사

  • 얕은 복사는 새로운 객체(변수)를 만든 후에 원본에 접근할 수 있는 참조(reference)를 입력한다.

🧨 list의 슬라이싱을 통한 새로운 값을 할당

  • 슬라이싱 연산을 통한 시퀀스 복제는 원본 시퀀스 자체나, 그 일부가 아닌 독립적인 사본이다.
  • 슬라이싱을 통해서 값을 할당하면 새로운 id가 부여되며, 서로 영향을 받지 않는다.
  • 하지만 리스트 자체의 id는 달라도 리스트 내부의 값들은 같은 값을 가진다.
  • 이는 mutable객체의 내부에 mutable 객체가 들어 있는지, immutable 객체가 들어 있는지에 따라 달라진다.
a = [1,2,3,[4,5,6]]
b = a[:]
id(a)
>>> 4396179528

id(b)
>>> 4393788808

# immutable 자료의 변경
a[0] = 33
print(a, b)
>>> [33, 2, 3, [4, 5, 6]], [1, 2, 3, [4, 5, 6]]
'''
immutable 자료의 경우 변경하려는 값에서만 재할당이 일어나 id값이 달라지지만
나머지 값에서는 id값이 그대로이므로 얕은복사라고 본다.
'''

# mutable 자료의 변경
a[3].append(7)
print(a, b)
>>> [33, 2, 3, [4, 5, 6, 7]], [1, 2, 3, [4, 5, 6, 7]]
'''
mutable 자료의 경우에는 재할당이 일어나는 것이 아닌, a와 b에서 동시에 참조하고 있는
원본 값에서 추가가 일어난다. -> 동일하게 얕은 복사라 볼 수 있다.
'''

# mutable 자료 내의 immutable 자료 변경
a[3][1] = 150
print(a, b)
>>> [33, 2, 3, [150, 5, 6, 7]], [1, 2, 3, [150, 5, 6, 7]]
'''
이와 같이 mutable 자료형 안의 immutable 자료형을 변경할 때는 a만을 복사하는 것이고
각 요소에 대해서는 복사가 아닌 배정이 이뤄졌다고 추측한다. 
'''

🧨 copy라이브러리의 copy메서드

  • 슬라이싱을 통한 복사와 동일하게 값을 할당하면 새로운 id가 부여되며, 서로 영향을 받지 않는다.
  • 또한 슬라이싱과 동일하게 리스트 자체의 id는 달라도 리스트 내부의 값들은 같은 값을 가진다.
  • copy메서드 역시 mutable객체의 내부에 mutable 객체가 들어 있는지, immutable 객체가 들어 있는지에 따라 달라진다.
import copy
a = [1,2,3,[4,5,6]]
b = copy.copy(a)
id(a)
>>> 4396179528

id(b)
>>> 4393788808

# immutable 자료의 변경
a[0] = 33
print(a, b)
>>> [33, 2, 3, [4, 5, 6]], [1, 2, 3, [4, 5, 6]]
'''
immutable 자료의 경우 변경하려는 값에서만 재할당이 일어나 id값이 달라지지만
나머지 값에서는 id값이 그대로이므로 얕은복사라고 본다.
또한 재할당의 경우 원본에 영향을 주지 못한다.
'''

# mutable 자료의 변경
a[3].append(7)
print(a, b)
>>> [33, 2, 3, [4, 5, 6, 7]], [1, 2, 3, [4, 5, 6, 7]]
'''
mutable 자료의 경우에는 재할당이 일어나는 것이 아닌, a와 b에서 동시에 참조하고 있는
원본 값에서 추가가 일어난다. -> 동일하게 얕은 복사라 볼 수 있다.
'''

# mutable 자료 내의 immutable 자료 변경
a[3][1] = 150
print(a, b)
>>> [33, 2, 3, [150, 5, 6, 7]], [1, 2, 3, [150, 5, 6, 7]]
'''
이와 같이 mutable 자료형 안의 immutable 자료형을 변경할 때는 a만을 복사하는 것이고
각 요소에 대해서는 복사가 아닌 배정이 이뤄졌다고 추측한다. 
'''

2. 깊은복사

🧨 copy라이브러리의 deepcopy메서드

  • 깊은복사는 객체뿐만 아니라 객체 내부의 값들까지 복사해 변수에 입력한다.
  • 깊은 복사를 통해 복사된 원본과 복사본은 같은 값을 가지지만 본질적으로 서로 다르기 때문에 한 변수가 수정되도 다른 변수에 영향을 미치지 않는다.
import copy
a = [1,2,3,[4,5,6]]
b = copy.copy(a)
id(a)
>>> 4396179528

id(b)
>>> 4393788878

# immutable 자료의 변경
a[0] = 33
print(a, b)
>>> [33, 2, 3, [4, 5, 6]], [1, 2, 3, [4, 5, 6]]
'''
본질적으로 다른 값을 참조하고 있으므로 한 값을 바꾼다 해서 다른 값에
영향을 미치지 않는다.
'''

# mutable 자료의 변경
a[3].append(7)
print(a, b)
>>> [33, 2, 3, [4, 5, 6, 7]], [1, 2, 3, [4, 5, 6]]
'''
mutable 자료 역시 본질적으로 다른 값이므로 영향을 받지 않는다.
'''

# mutable 자료 내의 immutable 자료 변경
a[3][1] = 150
print(a, b)
>>> [33, 2, 3, [150, 5, 6, 7]], [1, 2, 3, [4, 5, 6]]
'''
mutable 자료 내부의 immutable 자료의 변경 시에도 동일하게 영향을 받지 않는다.
'''

0개의 댓글