* 바쁘신 분들을 위한 3줄 요약
1. is는 변수가 저장된 장소가 같은 지 확인합니다.
2. ==는 변수의 값이 같은 지 확인합니다.
3. 숫자, 글자 같은 고정값(Literal)에 is 연산자를 사용하면 SyntaxWarning을 일으킵니다.
* 이 글에서 작성된 메모리 주소는 모두 가상입니다. 참고 바랍니다.
파이썬(python)에서는 "==" 연산자를 활용하여 같은 값인지 확인합니다.
>>> a = 0
>>> b = 0
>>> a == b
True
그런데 파이썬에는 is을 사용하는 경우도 있습니다.
>>> a = 0
>>> b = 0
>>> a is b
True
is와 == 는 동일한 연산자일까요? ==와 is는 무엇이 다른 걸까요?
is 연산자는 변수의 저장 위치가 같은지를 확인하는 연산자입니다.
변수의 저장 위치란 무엇일까요? 바로 메모리에 변수가 저장된 위치(혹은 주소)를 의미합니다. is라는 연산자가 어떻게 동작하는지를 알기 위해서는, 먼저 메모리 구조를 이해해야 합니다.
우리는 컴퓨터의 성능을 확인하기 위해 CPU, GPU가 얼마나 빠른지 확인합니다, SSD의 용량도 확인하지요, 그리고, 좀 더 세심한 분들은 RAM 용량도 확인합니다. 이 RAM을 우리는 다른 말로 "메모리"라고 부르며, python은 이 메모리 공간에 변수들을 저장합니다. 실제로 변수들이 어떻게 저장되어 있는지 그림 1을 통해 확인해 보겠습니다.
메모리에는 저장할 수 있는 공간마다 주소가 있고, 그 공간에 데이터를 담을 수 있습니다. id() 함수는 python에서 변수의 저장된 주소를 확인할 수 있는 함수입니다. 이 함수를 사용해서 a와 b의 저장된 주소를 확인할 수 있습니다.
* 참고로 "0x"는 숫자가 16진법(hex)으로 작성되어 있다는 뜻입니다. hex()는 숫자를 16진법으로 출력합니다.
a변수는 0x7FF804에 저장되어 있고, b변수는 0x7FFF32에 저장되어 있습니다. 서로 다른 공간에 서로 다른 값이 저장되어 있는 상황입니다. 이때, b를 1 증가시키면 하면 a와 b의 값은 같아집니다. 그렇다면 주소는 어떻게 될까요?
놀랍게도, b의 주소는 a로 옮겨지는 것도, b에 머무르는 것도 아닌, 새로운 주소(0x8FABD0)를 받게 됩니다.
* 사실 객체형식마다 새로운 주소를 받는 동작이 조금씩 다릅니다. 새로운 주소를 받는 것은 immutable의 특성입니다.
이제 a와 b는 같은 값(4096)이지만, 주소가 다릅니다(0x7FF804 vs 0x8FABD0). 이 상황에서 == 연산자와, is 연산자를 사용하면 어떻게 될까요?
>>> a == b
True
>>> a is b
False
값을 비교하는 "==" 연산자는 True를, 주소를 비교하는 "is" 연산자는 False를 보여줍니다.
그렇다면 만약 a와 b의 주소를 같게 만들면 "is" 연산자는 어떻게 동작할까요?
>>> a = 5000 ## a에 주소 할당
>>> b = a ## 여기서 b는 a와 같은 주소를 가진다.
>>> hex(id(a))
'0x15dc5ee' ## a의 주소.
>>> hex(id(a))
'0x15dc5ee' ## b의 주소. a와 같다.
>>> a == b
True
>>> a is b
True
b = a로 인해 b 주소는 a와 동일합니다. 그래서 a is b는 True를 보여줍니다. 그럼 a의 값을 변경하면 b의 값도 변경이 될까요? 그렇지 않습니다. a 값을 변경하면 a는 다른 주소로 가는 것을 그림 3에서 확인할 수 있습니다.
그럼 여기서 ==과 is를 마무리... 하기 전에 뭔가 이상하지 않나요?
## 이 글에서 가장 처음에 나왔던 is 예시
>>> a = 0
>>> b = 0
>>> a is b
True
이건 왜 True를 보여줄까요? a와 b를 따로 선언했으니 주소가 다르게 잡힐 텐데 말이죠.
python은 메모리 최적화를 위해, 미리 할당하는 숫자들이 있습니다.
파이썬 인터프리터(Cpython) 그 자체도 프로그램이기 때문에, 실행하면 Memory에 프로그램이 올라갑니다. 그리고 파이썬의 기능과 요소들도 메모리에 저장됩니다. 그중에는 파이썬에서 쓰이는 내장함수(예 : sum, abs, id 등)도 메모리에 자리 잡고 있는데 이 함수들이 자리 잡은 메모리 주소를 id() 함수로 확인할 수 있습니다.
>>> hex(id(sum))
'0x15dc5129ef'
이런 식으로 파이썬 내장 함수들이 메모리의 어디에 위치하는지 확인할 수 있습니다.
뿐만 아니라 True, False, None과 같은 값도 메모리 주소를 확인할 수 있습니다
>>> hex(id(True)), hex(id(False)), hex(id(None))
('0x15dc512968', '0x15dc512988', '0x15dc51FCC8')
파이썬은 여기서 멈추지 않고, -5 ~ 256까지 미리 메모리에 할당하여, 변수가 해당 범위 내에서 선언되면, 추가적인 메모리 주소를 사용하는 것이 아닌, 파이썬이 기존에 잡아놓은 메모리를 사용하게 합니다. 예를 들어, 값 1을 가진 변수 100개를 선언한다면, python integer(28byte)를 100번 할당하는 것이 아니라, address(8byte)만 100번 할당하게 되어 메모리공간을 효율적으로 사용할 수 있습니다.
*이러한 python메모리 관리를 Object interning이라 합니다.
실험을 하나 해보겠습니다. 만약에 변수 2개를 다르게 선언하고, 값을 같은 값으로 맞춘다면, 서로의 주소가 같아질까요?
네 맞습니다. 같은 주소를 가지게 됩니다.
>>> a = 10
>>> b = 100
>>> b -= 90
>>> a is b
True
이런 규칙으로 인해, 아래의 코드가 성립하는 것입니다.
>>> a = 0
>>> b = 0
>>> a is b
True
지금까지 is 연산자가 어떻게 동작하는지, 그리고 실제 python에서 메모리 주소는 어떻게 관리되는지 간단하게 알아보았습니다. 그럼 is연산자를 어떻게 사용하는 것이 좋을까요?
is 연산자는 False, None과 비교할 때 사용하는 것을 추천합니다
False, None과의 비교는 이미 python 내부 변수이므로, a = False로 새로 선언해도 값의 주소는 동일합니다. 이런 것들은 is로 비교하는 것이 가독성이나 성능면에서도 좋습니다.
## 좋은 예시
def func1(value):
if value is None or False: ## value가 False인지, None인지 확인한다
return False
if value == 5:
value = change(value)
return calculate(value)
## 안좋은 예시
def func2(value, flag):
if flag is True: ## if flag: 라고 하는 것이 좋습니다.
value = cal(value)
if flag is 5: ## 값 비교이기 때문에, if value == 5라 해야 합니다.
value = change(value)
return calculate(value)
mutable 한 변수를 다룰 때 is를 쓰면 더욱 좋은 효과를 노릴 수 있지만, 그 정도 Skill을 사용하시는 분들은 이미 이 글을 읽으실 필요가...
특히, 값을 비교할 때는 is 사용을 하지 않습니다. 특히 literal(상수, 글자 등)과의 비교는 SyntaxWarning을 일으킵니다.
>>> a = 0
>>> a is 0
<stdin>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
True
* reference
https://docs.python.org/3/c-api/long.html#c.PyLong_FromLong
https://realpython.com/python-memory-management/
'Python > Basic' 카테고리의 다른 글
Python - List Comprehension (0) | 2023.03.19 |
---|---|
Python - Int의 크기가 28bytes인 이유 (5) | 2023.03.01 |
Python - Mutable vs Immutable (2) | 2023.02.26 |
Python - 기본 문법정리 (0) | 2023.02.24 |
Python - List는 어떻게 데이터를 관리하는가? (0) | 2023.02.15 |