파이썬의 is 연산자와 == 연산자
파이썬에서는 두 객체를 비교할 때 is 연산자와 == 연산자를 사용합니다.
비슷해 보이지만 이 두 연산자는 비교하는 대상이 완전히 다른데요.
차이점을 정확히 이해하지 않으면 찾기 어려운 버그를 만들어낼 수 있습니다.
이번 포스팅에서는 is 연산자와 == 연산자가 정확히 무엇을 비교하는지, 그리고 언제 어떤 연산자를 써야 하는지 알아보겠습니다.
is 연산자 🆚 == 연산자
파이썬에서 is 연산자는 두 변수가 메모리 상에서 같은 객체를 가리키는지를 확인합니다.
반면 == 연산자는 두 객체의 값이 같은지를 비교합니다.
간단한 예로 리스트를 비교해볼까요?
>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> a == b
True
>>> a is b
False
a와 b는 같은 값을 담고 있어서 == 비교는 True가 나옵니다.
하지만 각각 별도로 생성된 리스트이기 때문에 메모리 상에서는 서로 다른 객체이고, is 비교는 False가 됩니다.
사전도 마찬가지입니다.
>>> {'a': 1} == {'a': 1}
True
>>> {'a': 1} is {'a': 1}
False
이처럼 is 연산자를 써야 할 곳에 ==를 쓰거나 그 반대로 하면 원치 않는 결과를 얻을 수 있습니다.
is 연산자와 == 연산자의 결과에서 왜 이런 차이가 생기는 걸까요? 🤔
동일성과 동등성
다른 프로그래밍 언어처럼 파이썬에도 동일성(Identity)과 동등성(Equality) 개념이 있는데요. 동일성은 두 변수가 메모리 상에서 같은 객체를 가리키는지를 의미하고, 동등성은 두 객체의 값이 같은지를 의미합니다.
is 연산자는 내부적으로 두 객체의 메모리 주소를 비교합니다.
파이썬의 내장 함수인 id()를 호출하면 객체의 고유한 메모리 주소를 확인할 수 있죠.
>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> id(a)
4334519952
>>> id(b)
4334520144
a와 b의 메모리 주소가 다르니까 a is b는 False가 됩니다.
그러면 b = a처럼 같은 객체를 가리키도록 하면 어떻게 될까요?
>>> a = [1, 2, 3]
>>> b = a
>>> a is b
True
>>> id(a)
4334520336
>>> id(b)
4334520336
이번에는 두 변수가 동일한 메모리 주소의 객체를 가리키고 있어서 is 비교가 True가 됩니다.
이해를 돕기 위해 메모리 구조를 시각화해보면 이렇습니다.
a ──→ [1, 2, 3] (주소: 4334520336)
↑
b ─────┘
c ──→ [1, 2, 3] (주소: 4334416960)
a와 b는 같은 객체를 가리키지만, c는 값은 같아도 별도의 객체인 거죠.
>>> a == b == c
True
>>> a is b
True
>>> a is c
False
eq() 메서드와 동등성
사실 == 연산자는 단순히 값을 비교하는 게 아니라, 해당 객체의 __eq__() 메서드를 호출한 결과를 반환합니다.
좀 극단적인 예로, __eq__() 메서드가 항상 참을 반환하도록 클래스를 작성해볼게요.
class AlwaysEqual:
def __eq__(self, other):
return True
이 클래스의 객체는 어떤 값과 비교해도 항상 True가 나옵니다.
>>> ae = AlwaysEqual()
>>> ae == 1
True
>>> ae == "hello"
True
>>> ae == None
True
하지만 is 연산자는 __eq__()와 전혀 관계가 없으므로, 별도로 생성한 객체끼리는 당연히 False입니다.
>>> ae1, ae2 = AlwaysEqual(), AlwaysEqual()
>>> ae1 == ae2
True
>>> ae1 is ae2
False
참고로 데이터 클래스를 사용하면 __eq__() 메서드가 자동으로 생성되기 때문에 이렇게 직접 구현할 필요가 줄어듭니다.
정수 캐싱의 함정
여기서 한 가지 알아둬야 할 게 있습니다. CPython(우리가 보통 쓰는 파이썬 구현체)은 자주 쓰이는 작은 정수(-5부터 256까지)를 미리 생성해서 메모리에 캐싱해둡니다.
>>> a = 256
>>> b = 256
>>> a is b
True
256 이하의 정수는 항상 같은 객체를 재사용하기 때문에 is 비교가 True로 나옵니다.
그런데 257 이상의 정수는 매번 새로운 객체로 생성됩니다.
>>> a = 257
>>> b = 257
>>> a is b
False
값은 같은데 is 비교 결과가 달라지죠?
이런 이유로 정수나 문자열의 값을 비교할 때 is 연산자를 쓰면 예상치 못한 버그가 발생할 수 있습니다.
숫자나 문자열의 값을 비교할 때는 반드시 == 연산자를 사용해야 합니다.
💡 Python 3.8부터는 리터럴 값에
is연산자를 사용하면SyntaxWarning: "is" with 'int' literal. Did you mean "=="?라는 경고가 발생합니다. 파이썬이 이런 실수를 미리 잡아주는 셈이죠.
언제 어떤 연산자를 써야 할까?
코딩을 하다 보면 객체의 메모리 주소보다는 값이 같은지를 비교할 일이 훨씬 많죠?
그래서 대부분의 경우 == 연산자를 쓰면 됩니다.
다만 PEP 8 스타일 가이드에서는 None, True, False와 비교할 때는 is 연산자를 쓰도록 권고합니다.
이 세 가지는 파이썬에서 싱글턴(singleton) 객체로, 프로그램 전체에서 딱 하나씩만 존재하기 때문입니다.
>>> a = None
>>> b = None
>>> a is b
True
>>> id(a) == id(b)
True
None 비교에 == 연산자를 써도 보통은 같은 결과가 나오긴 합니다.
하지만 is 연산자를 쓰면 메모리 주소를 바로 비교하기 때문에 더 효율적이고, 코드의 의도도 명확해지죠.
린터(linter)를 쓰신다면 is 연산자를 써야 하는 상황에서 == 연산자를 사용하면 다음과 같은 경고를 보실 겁니다.
E711 comparison to None should be 'if cond is None:'
if 조건문에서 True와 비교하는 경우도 종종 볼 수 있는데요.
# E712 comparison to True should be 'if cond is True:' or 'if cond:'
>>> if x == True:
... print('x is True')
이것보다는 is 연산자를 쓰는 게 낫고요.
>>> if x is True:
... print('x is True')
사실 그냥 if 절 안에 변수만 놓으면 더 파이썬다운(Pythonic) 코드가 됩니다.
>>> if x:
... print('x is truthy')
같은 이치로 False와 비교할 때도 not 연산자를 활용하면 깔끔합니다.
# 이것보다는
>>> if y == False:
... print('y is False')
# 이렇게, 아니면
>>> if y is False:
... print('y is False')
# 이렇게 쓰는 게 좋습니다
>>> if not y:
... print('y is falsy')
한 가지 주의할 점은, if x:는 x가 True인 경우뿐 아니라 truthy한 모든 값(1, "hello", [1] 등)에서 참이 된다는 겁니다.
정확히 True인지 확인해야 하는 드문 경우에는 if x is True:를 써야 합니다.
is not 연산자
is의 부정형으로 is not 연산자가 있습니다.
not (a is b) 대신 a is not b라고 쓸 수 있어서 영어 문장처럼 읽히죠.
>>> x = None
>>> x is not None
False
>>> x = 42
>>> x is not None
True
PEP 8에서도 not a is b보다 a is not b를 권장합니다.
None 체크를 할 때 if x is not None:으로 쓰는 패턴은 파이썬 코드에서 정말 자주 보실 겁니다.
마치며
is 연산자와 == 연산자의 핵심 차이를 정리하면 이렇습니다.
| 연산자 | 비교 대상 | 내부 동작 | 주요 용도 |
|---|---|---|---|
is | 동일성(Identity) | 메모리 주소 비교 | None, True, False 비교 |
== | 동등성(Equality) | __eq__() 메서드 호출 | 값 비교 (대부분의 경우) |
대부분의 상황에서는 == 연산자로 값을 비교하고, None이나 True, False 같은 싱글턴 객체와 비교할 때만 is 연산자를 쓰면 됩니다.
정수 캐싱처럼 CPython 구현에 따라 is의 결과가 달라지는 경우도 있으니, 값 비교에는 꼭 ==를 쓰는 습관을 들이시길 바랍니다.
파이썬의 비교 연산자를 더 깊이 이해하고 싶으시다면 PEP 8 스타일 가이드의 권고 사항이나 파이썬 공식 문서의 비교 연산을 참고해보세요.
This work is licensed under
CC BY 4.0