Logo

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

파이썬에서는 객체가 같은지 확인하기 위해서 is 연산자와 == 연산자, 이렇게 두 개의 연산자를 사용합니다. 이 두 연산자의 차이점을 정확히 이해하지 않고 잘못 쓰시면 큰 낭패를 보실 수 잇는데요.

이번 포스팅에서는 is 연산자와 == 연산자의 차이점에 대해서 알아보도록 하겠습니다.

is 연산자 🆚 == 연산자

파이썬에서 is 연산자는 두 개의 객체가 메모리 상에서 같은 위치에 있는지 확인할 때 사용하는 연산자입니다.

예를 들어, 숫자나 문자 간에 is 연산자로 비교해보면 참이 나옵니다.

>>> 1 is 1
True
>>> 'a' is 'a'
True

하지만, 리스트사전is 연산자로 비교해보면 거짓이 나오는 것을 볼 수 있습니다.

>>> [1] is [1]
False
>>> {'a': 1} is {'a': 1}
False

그런데 is 연산자 대신에 == 연산자를 쓰면 참이 나오죠?

>>> [1] == [1]
True
>>> {'a': 1} == {'a': 1}
True

이를 통해서 is 연산자를 쓰면 원치않는 결과를 얻을 수 있다는 것을 알 수 있습니다.

is 연산자와 == 연산자의 결과에서 왜 이러한 차이가 생길까요? 🤔

동일성과 동등성

다른 프로그래밍 언어처럼 파이썬에도 동일성(Identity)와 동등성(Equality) 개념이 있는데요. 동일성은 두 객체의 메모리 주소가 같음을 의미하고, 동등성은 두 객체의 값이 같음을 의미합니다. 그런데 사실 엄밀히 얘기해서 동등성은 해당 객체의 __eq__() 메서드의 구현에 달려 있습니다.

좀 극단적인 예로, __eq__() 메서드가 항상 참을 반환하도록 클래스를 하나 작성해볼께요.

class AlwaysEqual:
    def __eq__(self, other):
        return True

그리고 이 클래스의 객체를 하나 생성해보죠.

>>> ae = AlwaysEqual()

== 연산자를 사용해서 이 객체를 다른 어떤 객체와 동등성 비교를 해도 결과가 참이 나오는 것을 볼 수 있습니다.

>>> ae == 1
True
>>> ae == 'a'
True
>>> ae == [1]
True
>>> ae == {"a": 1}
True
>>> ae == None
True

하지만 이 클래스로 새로운 객체 2개를 생성해서 is 연산자로 동일성 비교를 해보면 거짓이 나오는 것을 볼 수 있습니다.

>>> ae1, ae2 = AlwaysEqual(), AlwaysEqual()
>>> ae1 is ae2
False

즉, 이 두 객체는 메모리 상에서 서로 다른 위치에 있다는 얘기인데요. 정확한 주소를 확인하시려면 각 객체를 상대로 파이썬의 내장 함수인 id()를 호출해보면 됩니다. (실제 메모리 주소는 저와 다르실 것 입니다.)

>>> id(ae1)
4334519952
>>> id(ae2)
4334520144

이번에는 객체를 생성해서 ae1 변수에 먼저 할당하고, ae1 변수에 할당되어 있는 객체를 ae2 변수에 할당해볼까요? 이 두 변수를 is 연산자로 동일성 비교를 해보면 참이 나오는 것을 볼 수 잇습니다.

>>> ae1 = AlwaysEqual()
>>> ae2 = ae1
>>> ae1 is ae2
True

즉, 이 두 변수는 메모리 상에서 동일한 위치에 있는 객체를 가리키고 있다는 뜻입니다.

>>> id(ae1)
>>> 4334520336
>>> id(ae2)
>>> 4334520336

정확히 이해하셨는지 확인하기 위해서 마지막으로 한 가지 예를 더 들어볼까요?

>>> a = [1]
>>> b = a
>>> c = [1]

이 3개의 리스트는 모두 같은 값을 갖기 때문에 == 연산자로 동등성 비교를 하면 모두 참이 나옵니다.

>>> a == b
True
>>> a == c
True
>>> c == a
True

하지만 is 연산자로 동일성 비교를 하면 ab 간에만 참입니다.

>>> a is b
True
>>> b is c
False
>>> c is a
False

이해를 돕기 위해서 메모리의 모습을 시각화해보았습니다.

a → [1] (주소: 4334416064)
  ↗
b

c → [1] (주소: 4334416960)

언제 어떤 연산자를 써야할까?

코딩을 할 때 우리는 객체의 메모리 주소에 대해서 크게 관심이 없죠? 하지만 객체의 값이 동일한지를 비교해야 할 때는 잦습니다. 그렇기 때문에 == 연산자를 사용해야 하는 경우가 대부분입니다.

하지만 PEP 8 스타일 가이드는 True, False, None과 비교할 때는 is 연산자를 쓰도록 권고합니다. 왜냐하면 이 3개의 특수 객체는 항상 고정된 메모리 주소를 갖기 때문입니다.

>>> a = None
>>> b = None
>>> a is b
True
>>> id(a)
4343746864
>>> id(b)
4343746864

그렇다고 해서 == 연산자를 썼을 때 is 연산자를 썼을 때와 결과가 다르게 나오는 것은 아닙니다. 단지 is 연산자를 사용하면 메모리 주소를 바로 비교하기 때문에 효율적이고 코드 가독성이 좋아집니다.

>>> a == b
True

린터(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 True')

같은 이치로 False와 비교할 때도 not 연산자를 활용하는 것이 좋겠죠?

# E712 comparison to False should be 'if cond is False:' or 'if not cond:'
>>> if y == False:
...     print('y is False')
>>> if y is False:
...     print('y is False')
>>> if not y:
...     print('y is False')

마치면서

지금까지 is 연산자와 == 연산자가 어떻게 다르고 각 연산자를 언제 사용해야되는지에 대해서 알아보았습니다. 객체가 동일한 메모리 주소를 참조하는지를 알아내야 할 때는 is 연산자를 사용해야하고, 객체의 값이나 __eq__() 메서드의 호출 결과를 기준으로 비교하려고 할 때는 == 연산자를 사용해야 합니다.