본 포스팅에서는 파이썬의 mutable 객체와 immutable 객체에 대해 is 연산자, id 함수, == 연산자를 중심으로 설명하고 있습니다.
2018. 5. 10 - 최초작성
2018. 7. 19
파이썬에서 모든 것은 객체(object)입니다.
객체가 생성된 후 ID는 변경되지 않으며 해당 객체가 프로그램 실행 중에 유일한 객체라는 것을 보장합니다. ( id 함수를 사용하면 객체의 ID를 확인할 수 있습니다. )
특정 객체가 저장된 메모리 주소를 가리키는 고유 ID라고 생각할 수 있지만 실제 메모리 주소는 아닙니다.
(CPython에서는 id 함수가 객체가 저장된 실제 메모리 주소를 리턴합니다.)
파이썬에서는 객체를 비교하기 위해 id 함수와 is 연산자가 제공됩니다.
id(object)
id 함수는 아규먼트로 제공된 객체를 위한 고유한 상수를 리턴합니다.
두 객체의 id 함수 리턴값이 같다면 두 객체는 동일한 객체입니다.
리턴값을 객체의 실제 메모리상의 주소라 보기는 힘들지만 해당 객체를 가리키는 유일한 상수입니다.
CPython의 경우에는 객체의 실제 메모리 주소를 사용합니다.
a is b
is 연산자는 a, b가 같은 객체라면(id 함수의 리턴값이 같다면) True를 리턴합니다.
is 연산자를 이용하여 다음처럼 비교해보면 객체의 값이 같지만 False를 리턴하는 경우가 있기 때문에 주의해야 합니다.
변수 is 변수
변수 is 객체
객체 is 객체
파이썬에서는 값이 같더라도 별개의 객체로 보는 경우가 대부분이기 때문입니다.
데이터를 비교할 때에는 == 연산자를 사용하여 두 객체의 값이 동일한지 테스트해야 합니다.
is 연산자는 같은 객체인지 확인하거나 객체가 파이썬 상수(None, True, False)와 같은지 확인할 때에만 사용하도록 합니다.
파이썬 3.6.5의 인터프리터를 기준으로 하고 있으며 PyCharm 같은 IDE에서는 실행 결과가 다른 경우가 있으니 주의해야 합니다. |
파이썬에는 mutable 데이터 타입과 immutable 데이터 타입이 있습니다.
mutable 객체
객체를 생성한 후, 객체의 값을 수정 가능, 변수는 값이 수정된 같은 객체를 가리키게됨, 예) list, set, dict
immutable 객체
객체를 생성한 후, 객체의 값을 수정 불가능, 변수는 해당 값을 가진 다른 객체를 가리키게됨, 예) int, float, complex, bool, string, tuple, frozen set
쉽게 구별하는 방법은 변수에 대입한 값을 수정했을 때 전후의 id 함수 리턴값을 비교해보는 겁니다.
같으면 mutable 객체, 다르면 immutable 객체입니다.
파이썬에서 변수는 자신에게 대입된 객체를 가리키는 일종의 포인터 같은 존재입니다.
C언어에서 대입(assignment)은 오른쪽에 있는 값을 변수에 저장하라는 의미입니다.
변수 선언시 저장할 데이터의 타입을 지정해줘야 하며 변수가 자체 저장공간을 할당받습니다.
int a = 1; // 변수 a와 1은 같은 존재라 볼 수 있습니다. |
파이썬 언어에서 대입은 오른쪽에 있는 값을 가지는 객체를 변수가 가리키라는 의미입니다.
변수 선언이라는 개념이 없으며 변수는 타입을 갖지 않습니다.
왜냐면 파이썬에서는 문자열이나 숫자, 리스트 같은 데이터 자체가 객체이기 때문입니다.
변수는 자체 저장공간을 할당 받지 않으며 객체를 가리키기만 합니다.
a = 1 // 변수 a와 1은 별개의 존재입니다. |
변수에 새로운 객체를 대입하거나 객체를 가리키고 있는 변수를 다른 변수에 대입하는 경우의 동작은 mutable 객체와 immutable 객체 간에도 차이가 있지만, 타입 별로도 차이가 있습니다.
>>> a = 1 # 변수 a에 정수 객체 1을 대입한 후
|
다른 객체를 포함하고 있는 복합 객체(compound object)가 아니라면 얕은 복사(shallow copy)와 깊은 복사(deep copy) 간에 차이가 없습니다.
똑같이 새로운 객체를 생성합니다. 복합 객체의 경우에 어떤 차이가 생기는지는 리스트 항목에서 다룹니다.
import copy d = copy.deepcopy(a) # 깊은 복사
# 얕은 복사와 깊은 복사간에 차이가 없습니다. |
정수(Integer)
파이썬에서 정수는 int 클래스 타입의 객체입니다.
>>> type(-5) |
정수 타입의 객체는 생성 후 값 수정이 불가능한 immutable 객체입니다.
>>> a = 10 |
>>> a = 10 # 변수 a가 가리키는 정수 객체에 1을 더합니다. 1508732208 # 객체 10은 그대로입니다. |
효율성을 위해 -5에서 256 사이의 정수는 캐시해 둡니다.
“The current implementation keeps an array of integer objects for all integers between -5 and 256, when you create an int in that range you actually just get back a reference to the existing object.“
https://docs.python.org/3/c-api/long.html#c.PyLong_FromLong
이 범위에 있는 정수의 경우에는 매번 새로운 객체가 생성되지 않고 기존에 메모리에 생성되어 있는 객체를 참조합니다.
변수에 대입하지 않은 정수 객체도 값이 같으면 같은 객체입니다.
>>> id(1) >>> id(1) 1755147280 |
경계값 벗어난 정수의 경우에는 같은 값을 가진 객체라도 동일한 객체가 아니기때문에 is 연산자로 객체의 값을 비교하면 안됩니다.
>>> id(257) >>> id(257) # 변수에 대입하지 않은 정수 257 객체는 계속 새로운 객체로 생성됩니다. >>> id(258) # 정수 257 객체가 아니더라도 같은 메모리 위치를 재사용합니다. >>> a = 257 >>> id(a) >>> id(257) 2127319722704 # 다른 메모리 위치에 정수 객체가 생성됩니다. >>> a == b # 변수 a, b가 가리키는 객체의 값은 같지만 >>> a is b # 변수 a,b가 가리키는 객체는 같지 않습니다. |
정수 객체의 값을 비교시 is 연산자 대신에 == 연산자를 사용해야 합니다.
-5 ~ 256 사이의 정수 경우에는 아무 문제 없어 보이지만
>>> a = 15 >>> a is b # 변수 a,b가 가리키는 객체는 같습니다. |
그외 범위의 정수에 대해 똑같은 연산을 하면 문제가 발생합니다.
>>> a = 1000 >>> a == b # 변수 a, b가 가리키는 객체의 값은 같지만 |
이외에도 여러가지 경우가 있기 때문에 정수 객체 비교시 is를 사용하면 안됩니다.
실수(float)
파이썬에서 실수는 float 클래스 타입의 객체입니다.
>>> type(0.1) |
실수 타입의 객체는 생성 후 값 수정이 불가능한 immutable 객체입니다.
>>> a = 0.1 >>> id(a) |
같은 값을 가진 실수 객체라도 동일한 객체가 아니기때문에 is 연산자로 객체의 값을 비교하면 안됩니다.
>>> id(1.1) |
객체의 값을 비교시 is 연산자 대신에 == 연산자를 사용해야 합니다.
>>> a = 1000.0 |
문자열(string)
파이썬에서 문자열은 str 클래스 타입의 객체입니다.
>>> type('abc') |
문자열 타입의 객체는 생성 후 값 수정이 불가능한 immutable 객체입니다.
>>> a = 'abc' >>> id(a) |
>>> a = 'abc' # 변수 a가 문자열 객체를 가리키도록 합니다. |
문자열이 a ~ z, A ~ Z, 0~9, _ 문자로만 구성되는 경우에는 기존에 생성된 객체가 있다면 해당 객체를 참조합니다.
>>> a = 'azAZ09_' |
id 함수가 똑같은 문자열에 대해 같은 값을 리턴합니다.
>>> id('azAZ09_') |
a ~ z, A ~ Z, 0~9, _ 외 문자가 문자열에 포함되어 있으면 변수에 똑같은 문자열을 대입하더라도 그 때마다 새로운 객체를 생성합니다.
>>> a = 'azAZ09_!' # 변수 a와 b에 똑같은 문자열을 대입했지만 False # 문자열에 a~z, A~Z, 0~9, _ 외의 문자가 포함되어 있다면 # 다음처럼 is 연산자로 비교시 False가 되므로 주의해야 합니다. |
a~z, A~Z, 0~9, _만으로 구성된 문자열 객체의 값을 비교하더라도 is 연산자를 사용하면 안됩니다.
변수에 대입 전에 문자열을 조합하는 것은 문제없어보이지만
>>> a = 'a' + 'bc' |
다음처럼 변수 a1, a2에 따로 문자열을 대입한 후, 변수 a에서 조합하여 대입하면 is 연산자로 비교시 문제가 생깁니다.
>>> a1 = 'a' True >>> a is b # 변수 a, b가 가리키는 객체는 별개의 객체입니다. True >>> a is 'abc' # is 연산자로 비교하면 False가 리턴됩니다. |
동일한 문자로 구성된 문자열을 항상 같은 객체로 처리하고 싶으면 sys.intern 메소드를 사용하면 됩니다.
>>> import sys |
input 함수등을 사용하여 키보드 입력받은 후 값 비교시에도 is를 사용하면 안됩니다.
ascii 한 문자를 입력으로 받아서 비교시에는 문제가 없지만
>>> a = input("input sth=") |
문자열을 입력하여 is 연산자로 비교해보면 값이 같은 객체인데도 False를 리턴합니다.
>>> a = input("input sth=") |
리스트(list)
파이썬에서 문자열은 list 클래스 타입의 객체입니다.
>>> type([1, 2, 3]) |
리스트 타입의 객체는 생성 후 값 수정이 가능한 mutable 객체입니다.
>>> a = [1, 2, 3] |
>>> a = [1, 2, 3] # 리스트 객체 [1, 2, 3]을 변수 a가 가리킵니다. True
>>> a, b |
두 변수에 대입된 리스트의 원소가 같더라도 다른 객체입니다.
>>> a = [1, 2, 3] >>> a is b # 두 객체는 별개의 객체입니다.
>>> a is b |
리스트 객체의 원소가 immutable 객체인 경우에는 얕은 복사(shallow copy)와 깊은 복사(deep copy)간에 차이가 없습니다.
>>> import copy >>> a == b == c # shallow copy, deep copy하면 원본 리스트와 값이 같습니다. (2202655015560, 2202656698888, 2202656701704) |
리스트 객체의 원소가 mutable 객체인 경우에는 얕은 복사를 하면 새로 생성된 리스트 객체의 원소가 기존 리스트 객체의 원소를 참조합니다.
>>> import copy |
깊은 복사를 해주면 객체의 원소도 새로운 객체가 됩니다.
>>> import copy |
튜플(tuple)
파이썬에서 튜플은 tuple 클래스 타입의 객체입니다.
>>> type((1,2,3)) |
튜플 타입의 객체는 생성 후 값 수정이 불가능한 immutable 객체입니다.
>>> a = (1, 2, 3) |
똑같은 값을 가지는 튜플이라도 다른 객체로 생성됩니다.
>>> a = (100, 200, 300) |
튜플이 immutable 객체이지만 객체의 값으로 mutable 객체에 대한 참조를 포함할 수 있습니다.
튜플은 값을 변경할 수 없는 객체이지만 원소가 참조하고 있는 mutable 객체의 값 변경은 가능합니다.
>>> a = (1 , 'abc', [1, 2, 3])
|
참고
https://stackoverflow.com/questions/306313/is-operator-behaves-unexpectedly-with-integers
https://stackoverflow.com/questions/13650293/understanding-pythons-is-operator
https://stackoverflow.com/questions/132988/is-there-a-difference-between-and-is-in-python
https://stackoverflow.com/questions/26595/is-there-any-difference-between-foo-is-none-and-foo-none
https://stackoverflow.com/questions/17599175/python-list-and
https://docs.python.org/3/library/index.html
https://docs.python.org/3/reference/datamodel.html
'Python > Python 예제 코드' 카테고리의 다른 글
Python에서 실수 출력 포맷 지정하기 (0) | 2021.11.10 |
---|---|
Python - CSV 파일을 순서 유지한채 무작위로 샘플링하여 두 개의 CSV 파일로 분할하기 (0) | 2021.10.04 |
실수 넘파이 배열을 소수점 자리 맞추어 공백없이 쉼표구분으로 출력하기 (0) | 2021.09.11 |
Python에서 C언어 스타일의 조건 처리 전처리문 사용하기 (0) | 2021.06.09 |
Python에서 코드 실행 시간 측정(perf_counter, process_time, timeit) (3) | 2019.08.27 |
시간날때마다 틈틈이 이것저것 해보며 블로그에 글을 남깁니다.
블로그의 문서는 종종 최신 버전으로 업데이트됩니다.
여유 시간이 날때 진행하는 거라 언제 진행될지는 알 수 없습니다.
영화,책, 생각등을 올리는 블로그도 운영하고 있습니다.
https://freewriting2024.tistory.com
제가 쓴 책도 한번 검토해보세요 ^^
그렇게 천천히 걸으면서도 그렇게 빨리 앞으로 나갈 수 있다는 건.
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!