Skip to content
Happy Programming Guide
Start learning
Python

Python is vs ==: When to Use Which

== asks whether two values are equal; is asks whether they are the same object. Use is only for None, True, False and identity checks. Here is why, with the cases that surprise people.

A person working on a laptop

== compares values: are these two things equal? is compares identity: are these two names pointing at the very same object in memory? For almost everything, you want ==. The one place you should always use is is comparing with None. Mixing them up usually works by accident, until the day it does not.

Equal versus the same#

Python
a = [1, 2, 3]
b = [1, 2, 3]
c = a

a == b        # True  - same contents
a is b        # False - two separate lists that happen to match
a is c        # True  - c is another name for the same list

print(id(a), id(b), id(c))    # b has a different id; a and c share one

id() returns a number unique to each live object. is is exactly id(x) == id(y). Two lists built separately are two objects; assigning one name to another makes two names for one object. Change c and a changes with it, because there is only one list.

None: always is#

Python
if result is None:        # correct
if result == None:        # works, usually - but wrong
if not result:            # different again: also true for 0, "", [], False

There is exactly one None object, so identity is the precise question. == None asks the object how it compares with None, which a class can override to return anything; some libraries’ objects return arrays or raise from ==. is None cannot be intercepted. The style guide for Python, PEP 8, says to use is, and linters flag the alternative.

The third form, not result, is a different check altogether. It is right when you mean “empty or missing” and wrong when zero or an empty string is a legitimate value.

Why is sometimes appears to work for values#

Python
a = 5
b = 5
a is b            # True - but not for the reason you think

a = 1000
b = 1000
a is b            # False, or True, depending on how the code was run

CPython keeps one shared object for each small integer, roughly -5 to 256, and reuses it. So two variables set to 5 point at the same object and is says True. Larger integers, and most strings and floats, are created fresh, so is says False — except when the compiler happens to combine identical constants in the same file, which makes it True again. None of this is guaranteed by the language; it is an implementation detail.

Python 3.8 and later warn about this: SyntaxWarning: "is" with a literal. Did you mean "=="?. Heed it.

Where is belongs#

  • x is None, x is not None
  • x is True, x is False — only when you specifically need the boolean object and not anything truthy. Usually if x: is what you want.
  • Sentinel objects: a unique marker meaning “not provided”, when None is a valid value:
Python
_MISSING = object()

def get(key, default=_MISSING):
    if default is _MISSING:
        raise KeyError(key)
    return default
  • Explicit identity checks: “is this the same object I put in the cache?” or detecting aliasing.

Everywhere else, ==.

Where == belongs#

  • Numbers, strings, lists, tuples, dictionaries, sets: if name == "admin", if total == 0.
  • Comparing instances of your own classes by their contents.
  • Anything a user typed, anything read from a file, anything computed.

Defining == for your own classes#

By default, two instances of a class are equal only if they are the same object — == falls back to is. To compare by contents, define __eq__:

Python
class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __eq__(self, other):
        if not isinstance(other, Point):
            return NotImplemented
        return (self.x, self.y) == (other.x, other.y)

    def __hash__(self):
        return hash((self.x, self.y))

Point(1, 2) == Point(1, 2)      # True
Point(1, 2) is Point(1, 2)      # False, always

A dataclass generates __eq__ for you, which is one of the reasons to use one. If you define __eq__ you should define __hash__ as well, or the class stops working as a dictionary key.

Performance#

is is a single pointer comparison and cannot be overridden, so it is marginally faster. That is not a reason to use it for values; the difference is negligible and the semantics are wrong. Use it where identity is the question and nowhere else.

Questions people ask#

Is “is not” the same as “not … is”?

Yes: x is not None and not (x is None) are equivalent, and the first reads better.

Why does == on two NumPy arrays not return a boolean?

Because NumPy overrides == to compare element by element and return an array. That is precisely why == None is unsafe: a library can make it mean anything.

Does is work for comparing types?

type(x) is int is a valid identity check, since each class is a single object. isinstance(x, int) is usually better because it respects subclasses.

What about != and is not?

Same rules: != for values, is not for None and identity.

Where to go next#

Shallow vs deep copy: when two names share one objectRead next

Keep reading

Keep going — pick your next guide

The fastest way to improve is to read one guide, then build the thing it describes. Start with the basics, or jump straight to a project.

Ask a question or share what worked

Your email address will not be published. Required fields are marked *