일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 29 | 30 |
- 소수
- 오토인코더
- 오일러 프로젝트
- 전처리
- project euler
- 역전파법
- 신경망
- 소인수분해
- Autoencoder
- deep learning
- 히토요시
- Convolutional Neural Network
- 냥코 센세
- 합성곱 신경망
- mnist
- CNN
- 자전거 여행
- 역전파
- 베이지안
- 딥러닝
- bayesian
- neural network
- A Neural Algorithm of Artistic Style
- SQL
- 비샤몬당
- Gram matrix
- 수달
- Python
- backpropagation
- c#
- Today
- Total
통계, IT, AI
[python] mutable vs immutable 본문
1. mutable vs immutable
파이썬의 오브젝트를 나누는 기준의 하나는 mutable 여부이다. mutable하지 않은 오브젝트는 immutable하다고 한다. mutable 오브젝트는 내용을 수정할 수 있고 immutable한 오브젝트는 그럴 수 없다. mutable한 오브젝트의 대표적인 예는 리스트list 등이 있고 immutable한 오브젝트는 튜플tuple, 문자열string 등이 있다.
리스트와 같은 mutable 오브젝트는 다음과 같이 내용을 수정할 수 있다.
1 2 3 | lst_1 = [ 1 , 2 , 3 , 4 ] lst_1[ 0 ] = 100 print (lst_1) # [100, 2, 3, 4] |
그런데 아래의 코드는 어떻게 작동 하는 것일까? immutable한 오브젝트는 '내용을 수정 할 수 없다'고 하지 않았는가?
1 2 3 4 5 6 | 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) |
사실 위의 코드는 문자열과 튜플을 수정하는 것이 아니라 새로 생성하는 것이다. 비유하자면, 책을 복사하면서 특정 부분만 원하는 내용으로 바꿔치는 것과 같다. 즉, 위의 코드에 등장하는 문자열과 튜플은 원본이 아니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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 여부에 대해서 명확하게 인지하지 못하면 코드가 의도치않은 방향으로 작동할 수 있다. 특히 오브젝트를 복사하고 수정하는 코드에서 주의를 기울여야 한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | # 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 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 29 30 31 32 | 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) |
4. hashable
파이썬 문서에서는 hashable에 대해서 다음과 같이 적고 있다. 어떤 오브젝트가 hashable하다는 것은, 그 오브젝트의 전 생애(lifetime)에 걸쳐 hash 값이 변하지 않고 그 값을 다른 오브젝트와 비교 가능하다는 것을 의미한다. 모든 immutable한 오브젝트는 hashable하며 mutable한 오브젝트는 그렇지 않다. 어떤 오브젝트가 hashable하면 그 오브젝트를 dict에서 키로 사용할 수 있다. 그래서 문자열 뿐만 아니라 튜플도 dict의 키가 될 수 있다. 이것이 중요한 이유는 dict의 키가 빠른 검색을 보장하기 때문이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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를 사용해서는 리스트를 복사할 수 없음을 보였다. 그렇다면 리스트를 복사하기 위해서는 어떻게 해야 할까? [:]를 사용하면 된다.
1 2 3 4 5 6 7 | 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로 채우는 것이라 한다. 간단히 예를 들면 다음과 같다.
1 2 3 4 5 6 7 8 9 | 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를 사용하면 된다.
1 2 3 4 5 6 7 8 9 10 | 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
1 2 3 4 5 6 7 | 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 |