mutable vs immutable
Python의 자료 형 중에 mutable 한 자료형과 immutable한 자료형이 있다.
mutable은 값이 변할수 있는 자료형
immutable은 값이 변할수 없는 자료형 의미
immutable 자료형: int, float, str, bool, tuple + complex, frozenset, bytes
mutable 자료형: list, set, dict + deque, bytearray, array.array
* immutable 여부 확인해보기
특정 index를 잡아서 새로 값 할당시 아래와 같은 TypeError 발생시 immutable임을 확인 가능
a = bytes([1, 2, 3])
print(a)
a[0] = 1
# TypeError: 'bytes' object does not support item assignment
* 값을 변화 시킬 경우 경우 mutable과 immutable의 결과 비교
a = 'a'
print('a_id:', id(a), "/ a:", a)
a += 'a'
print('a_id:', id(a), "/ a:", a)
b = [1, 2]
print('b_id:', id(b), "/ b:", b)
b.append(3)
print('b_id:', id(b), "/ b:", b)
b.pop()
print('b_id:', id(b), "/ b:", b)
# a_id: 2455722592368 / a: a
# a_id: 2455722823344 / a: aa
# b_id: 2455722250944 / b: [1, 2]
# b_id: 2455722250944 / b: [1, 2, 3]
# b_id: 2455722250944 / b: [1, 2]
아래의 출력 결과를 보면, a = 'a'의 a의 id와 a += 'a'를 한 이후 a의 id를 비교했을 때
변수 a의 id 값이 달라진 것을 확인할 수 있다. (메모리 상의 주소 변경)
그러나 list를 담은 변수 b의 id는 append, pop 이후에도 id 값이 동일하게 유지된다. (메모리 상의 주소 변동X)
이와 같이 변수의 값을 변동시킬 경우
immutable한 경우는 변수에 새로운 메모리 주소 할당
mutable한 경우는 메모리 주소 변동 없이 값 변경 가능
얕은 복사 (shallow copy) vs 깊은 복사 (deep copy)
list를 다룰 때 실수가 생기는 경우가 많은데
a = [1, 2, 3]
b = a
b.append(4)
print(a)
print(b)
# [1, 2, 3, 4]
# [1, 2, 3, 4]
위와 같이 b = a로 선언하고 b의 값을 변경하면, a도 함께 변한다.
이는 [1, 2, 3]에서 list의 첫 요소가 가르키는 메모리 주소를, a, b, 변수 모두 바라보도록 되어 있기 때문이다.
즉, 이는 list [1, 2, 3]의 주소값이 b에 복사된 것이다.
이렇게 주소값이 복사된 경우를 '얕은 복사(shallow copy)'라 한다.
주소값만이 아니라 그 안의 요소들을 모두 복사해서 개별적으로 list를 복사해서 가지려면 아래와 같이 copy를 활용한다.
a = [1, 2, 3]
b = a.copy()
b.append(4)
print(a)
print(b)
# [1, 2, 3]
# [1, 2, 3, 4]
이렇게 주소값을 복사하는 것이 아니라 변수가 가르키는 값을 복사해서 새로운 메모리 주소를 할당받도록 변수를 만드는 것을 '깊은 복사(deep copy)'라 한다.
* 2차원 배열 복사하기!
2차원 배열은 copy()만으로 깊은 복사가 되지 않는다.
2차원 배열은 리스트 안에 리스트가 담긴 형태인데,
내부에 담긴 리스트도 메모리 주소 정보를 참조해서 값이 할당되어 있기 때문에,
가장 바깥 리스트에 대해서만 .copy()로 할 경우에는 내부 리스트들은 얕은 복사만 된다.
a = [[1, 2], [3, 4]]
b = a.copy()
b[0].append('a')
print(a)
print(b)
# [[1, 2, 'a'], [3, 4]]
# [[1, 2, 'a'], [3, 4]]
이 경우는 python library인 copy.deepcopy를 통해서 내부까지 깊은 복사를 한다.
from copy import deepcopy
a = [[1, 2], [3, 4]]
b = deepcopy(a)
b[0].append('a')
print(a)
print(b)
# [[1, 2], [3, 4]]
# [[1, 2, 'a'], [3, 4]]
이는 중첩 딕셔너리 에서도 동일하므로 중첩 딕셔너리의 내부 딕셔너리도 주소가 아닌 값을 복사하려면 deepcopy 이용!
* reference count, Garbage collector
1) a = 'a'
2) a += 'a'
1) 에서의 id(a)와
2) 에서의 id(a)가 달라진 것은 변수 a가 메모리에 새로운 주소를 할당 받았기 때문이다.
그럼 1)에서의 문자열 'a'는 어떻게 되었을까?
reference count
특정 값을 바라보는 변수가 몇개 있는지를 reference count라 칭하는데
문자열 'a'는 변수 a에 할당되고, 이 때 문자열 'a'를 바라보는 변수 a가 생기면서
문자열 'a'에 대한 reference conunt 가 하나 증가해서 1이 된다.
a = 'a' -> 문자열 'a'의 reference count = 1
2) 에서 a+= 'a'를 함으로써 변수 a는 'aa' 값을 갖도록 reference가 변경 (메모리 상의 주소 변경)
이 때 기존 1)의 문자열 'a'는 메모리 상에 존재하지만,
'a'를 참조하는 변수는 사라지게 되면서, reference count가 하나 감소해서 reference count가 0이 된다.
문자열 'a'의 reference count = 0
Python에서는 reference count가 0이 되는 값들을 Garbage collector가 모아서 메모리에서 값을 해제해준다.
'Python' 카테고리의 다른 글
멀티프로세싱, 멀티스레딩 이해하기 with Python (0) | 2023.12.02 |
---|---|
Python: Generator # 제너레이터 # yield (2) | 2023.12.01 |
Python: del vs slice 성능 비교 (0) | 2023.11.23 |
Python: Union[bool, None] = None # Type hint (0) | 2023.11.20 |
Python: Error handling # 에러를 어떻게 다룰지를 쉽게 파악해보자 (0) | 2023.09.09 |