파이썬의 is 연산자와 == 연산자

파이썬의 is 연산자와 == 연산자

파이썬에서는 두 객체를 비교할 때 is 연산자와 == 연산자를 사용합니다. 비슷해 보이지만 이 두 연산자는 비교하는 대상이 완전히 다른데요. 차이점을 정확히 이해하지 않으면 찾기 어려운 버그를 만들어낼 수 있습니다.

이번 포스팅에서는 is 연산자와 == 연산자가 정확히 무엇을 비교하는지, 그리고 언제 어떤 연산자를 써야 하는지 알아보겠습니다.

is 연산자 🆚 == 연산자

파이썬에서 is 연산자는 두 변수가 메모리 상에서 같은 객체를 가리키는지를 확인합니다. 반면 == 연산자는 두 객체의 값이 같은지를 비교합니다.

간단한 예로 리스트를 비교해볼까요?

>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> a == b
True
>>> a is b
False

ab는 같은 값을 담고 있어서 == 비교는 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

ab의 메모리 주소가 다르니까 a is bFalse가 됩니다.

그러면 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)

ab는 같은 객체를 가리키지만, 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 연산자를 써야 하는 상황에서 == 연산자를 사용하면 다음과 같은 경고를 보실 겁니다.

Ruff / flake8 경고
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:xTrue인 경우뿐 아니라 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 CC BY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord