일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 역전파법
- Python
- Gram matrix
- 자전거 여행
- backpropagation
- 신경망
- CNN
- 합성곱 신경망
- mnist
- 오토인코더
- 베이지안
- 수달
- deep learning
- SQL
- project euler
- neural network
- 딥러닝
- 소수
- Convolutional Neural Network
- c#
- 히토요시
- 역전파
- 소인수분해
- 냥코 센세
- A Neural Algorithm of Artistic Style
- 오일러 프로젝트
- bayesian
- Autoencoder
- 비샤몬당
- 전처리
- Today
- Total
통계, IT, AI
[python] mutable vs immutable 본문
1. mutable vs immutable
파이썬의 오브젝트를 나누는 기준의 하나는 mutable 여부이다. mutable하지 않은 오브젝트는 immutable하다고 한다. mutable 오브젝트는 내용을 수정할 수 있고 immutable한 오브젝트는 그럴 수 없다. mutable한 오브젝트의 대표적인 예는 리스트list 등이 있고 immutable한 오브젝트는 튜플tuple, 문자열string 등이 있다.
리스트와 같은 mutable 오브젝트는 다음과 같이 내용을 수정할 수 있다.
lst_1 = [1, 2, 3, 4] lst_1[0] = 100 print(lst_1) # [100, 2, 3, 4]
그런데 아래의 코드는 어떻게 작동 하는 것일까? immutable한 오브젝트는 '내용을 수정 할 수 없다'고 하지 않았는가?
str_1 = 'abcde' str_1 = str_1.replace('c', 'x') print(str_1) # abxde tup_1 = (1, 2, 3) tup_1 += (4, ) print(tup_1) # (1, 2, 3, 4)
사실 위의 코드는 문자열과 튜플을 수정하는 것이 아니라 새로 생성하는 것이다. 비유하자면, 책을 복사하면서 특정 부분만 원하는 내용으로 바꿔치는 것과 같다. 즉, 위의 코드에 등장하는 문자열과 튜플은 원본이 아니다.
lst_1 = [1, 2, 3, 4] org_id = id(lst_1) lst_1[0] = 100 mod_id = id(lst_1) print('same list?', org_id == mod_id) # same list? false str_1 = 'abcde' org_id = id(str_1) str_1 = str_1.replace('c', 'x') mod_id = id(str_1) print('same string?', org_id == mod_id) # same string? False tup_1 = (1, 2, 3) org_id = id(tup_1) tup_1 += (4, ) mod_id = id(tup_1) print('same tuple?', org_id == mod_id) # same tuple? False
2. 복사
자신이 다루는 오브젝트의 mutable 여부에 대해서 명확하게 인지하지 못하면 코드가 의도치않은 방향으로 작동할 수 있다. 특히 오브젝트를 복사하고 수정하는 코드에서 주의를 기울여야 한다.
# mutable object lst_1 = [1, 2, 3] lst_2 = lst_1 print('same list?', id(lst_1) == id(lst_2)) lst_2[0] = 100 print(lst_1) # [100, 2, 3] print(lst_2) # [100, 2, 3] print('same list?', id(lst_1) == id(lst_2)) # immutable object str_1 = 'abcde' str_2 = str_1 print('same string?', id(str_1) == id(str_2)) # same string? True str_2 = str_2.replace('a', 'q') print(str_1) # abcde print(str_2) # qbcde print('same string?', id(str_1) == id(str_2)) # same string? False
나도 이와 같은 실수를 저지른 적이 있다. 신경망을 학습시키면서 가중치 매트릭스를 업데이트 해야 했는데 '복사'가 의도한 대로 되지 않았던 것이다. 복사본을 변경하니 원본도 변경되어 애를 먹었고 그 사실을 깨닫는 데에도 꽤 시간이 걸렸다. 그렇다면 리스트와 같은 오브젝트를 복사하기 위해선 어떻게 해야할까? 이를 5.1. 복사에서 다룬다.
3. 속도
immutable한 오브젝트가 수정될 때 복사본이 만들어진다는 사실은 코드의 수행 속도에도 영향을 준다. 이를 확인하기 위해서 \(1\)부터 \(10^k\)까지의 정수를 서로 연결하는 상황을 가정해 보았다.
import datetime as dt # immutable way def immutable_concat(k): start = dt.datetime.now() str_result = '' for n in range(10**k): str_result += str(n) return str_result, dt.datetime.now() - start # mutable way def mutable_concat(k): start = dt.datetime.now() str_result = ''.join(map(str, (n for n in range(10**k)))) return str_result, dt.datetime.now() - start # 10^5 case k = 5 _, immutable_time = immutable_concat(k) _, mutable_time = mutable_concat(k) print('10^5 case') print('immutable:', immutable_time) print('mutable:', mutable_time) # 10^6 case k = 6 _, immutable_time = immutable_concat(k) _, mutable_time = mutable_concat(k) print('10^6 case') print('immutable:', immutable_time) print('mutable:', mutable_time)
\(k\)가 작을 때에는 특별한 차이가 없지만 큰 \(k\)에 대해서는 큰 차이가 있음을 확인할 수 있다. 특기할 만한 것은 \(k=5\)과 \(k=6\)의 속도 차이이다. mutable한 방식을 사용하면 10배 정도의 차이를 보이는데 이는 어느 정도 이해가 간다. 데이터가 10배로 늘었기 때문이다. 그런데 문자열을 직접 붙이는 방식은 그 차이가 10배보다 더 크다.
4. hashable
파이썬 문서에서는 hashable에 대해서 다음과 같이 적고 있다. 어떤 오브젝트가 hashable하다는 것은, 그 오브젝트의 전 생애(lifetime)에 걸쳐 hash 값이 변하지 않고 그 값을 다른 오브젝트와 비교 가능하다는 것을 의미한다. 모든 immutable한 오브젝트는 hashable하며 mutable한 오브젝트는 그렇지 않다. 어떤 오브젝트가 hashable하면 그 오브젝트를 dict에서 키로 사용할 수 있다. 그래서 문자열 뿐만 아니라 튜플도 dict의 키가 될 수 있다. 이것이 중요한 이유는 dict의 키가 빠른 검색을 보장하기 때문이다.
k=7 dict_dat = {n: None for n in range(10**k)} list_dat = [n for n in range(10**k)] start = dt.datetime.now() if 10**k - 1 in dict_dat: print('found in dict', dt.datetime.now() - start) start = dt.datetime.now() if 10**k - 1 in list_dat: print('found in list', dt.datetime.now() - start)
위의 예시는 오브젝트의 hashable한 성질을 이용하면 더 빠른 검색이 가능하다는 것을 보인 것이다.
5. 기타
5.1. 오브젝트 복사
= operator를 사용해서는 리스트를 복사할 수 없음을 보였다. 그렇다면 리스트를 복사하기 위해서는 어떻게 해야 할까? [:]를 사용하면 된다.
lst_1 = [1, 2, 3, 4] lst_2 = lst_1[:] print('same id?', id(lst_1) == id(lst_2)) # same id? False lst_1[0] = 100 print(lst_1) # [100, 2, 3, 4] print(lst_2) # [1, 2, 3, 4]
하지만 이 복사는 얕은 복사(shallow copy)이다. 파이썬 문서에서는 얕은 복사란 해당 오브젝트를 복사하여 새로 구성하되 내용물은 reference로 채우는 것이라 한다. 간단히 예를 들면 다음과 같다.
lst_1 = [1, [2, 3], 4] lst_2 = lst_1[:] print('same id?', id(lst_1) == id(lst_2)) # same id? False lst_1[0] = 100 # lst_2의 첫번째 원소에 영향을 미치지 않는다. lst_1[1][0] = 100 # lst_2의 두번째 원소의 첫번째 원소에 영향을 미친다. print(lst_1) # [100, [100, 3], 4] print(lst_2) # [1, [100, 3], 4]
어떤 오브젝트의 전체를 복사하고자 한다면 copy 패키지의 deepcopy를 사용하면 된다.
import copy lst_1 = [1, [2, 3], 4] lst_2 = copy.deepcopy(lst_1) print('same id?', id(lst_1) == id(lst_2)) # same id? False lst_1[0] = 100 # lst_2에 아무런 영향을 미치지 않는다. lst_1[1][0] = 100 # lst_2에 아무런 영향을 미치지 않는다. print(lst_1) # [100, [100, 3], 4] print(lst_2) # [1, [2, 3], 4]
왜 굳이 복사 방식을 두가지로 설정해 두었을까? 이유는 두가지가 있다. 첫째는 자기 자신을 참조하는 오브젝트를 복사하게 되면 recursive loop가 발생할 수 있기 때문이다. 두번째 이유는 deep copy가 수행되면 원본과 공유해야 하는 데이터까지도 복사해 버리기 때문이다.
5.2. 튜플 안의 리스트는 수정 가능할까?
튜플 안에 리스트는 수정 가능할까? 이런 코드를 작성하게 될 일은 보통 없지만 시도해 보았다. 결론은, 수정 가능하다. 그런데 다소 특이하게 동작한다. 튜플의 원소를 변경하려는 시점에서 예외가 발생하지만 변경은 된다. 1
try: a = ([1, 2, 3], 4) a[0] += [4] except Exception as e: print(e) # 'tuple' object does not support item assignment finally: print(a) # ([1, 2, 3, 4], 4)
- Python 3.6.2 [본문으로]
'IT > 기타' 카테고리의 다른 글
[TensorFlow] 모델 저장 및 복원 (0) | 2017.09.06 |
---|---|
[기타] 티스토리 기초 설정 (1) | 2017.07.09 |
C#에서 이벤트 사용하기 (1) | 2017.01.29 |
C#에서 Zip 및 익명형식의 배열 사용하기 (0) | 2016.11.14 |
C#과 R을 연동하기 (2) | 2016.09.27 |