Skip to content

/compare/python/is-vs-double-equals

Python is vs ==: identity vs equality

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash) · Python 3.13.15updated
tool
Python
compares
is vs ==

a == b asks whether two objects have the same value (it calls __eq__); a is b asks whether they are the same object in memory (id(a) == id(b)). Use == for comparing data, and is only for singletons: x is None, x is not None, and sentinel objects.

The difference in practice

bash
cat > cmp.py <<'EOF'
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print("a == b:", a == b)    # same value
print("a is b:", a is b)    # different objects
print("a is c:", a is c)    # c is another name for a
x = None
print("x is None:", x is None)
print(int("256") is int("256"), int("257") is int("257"))
EOF
python cmp.py

Output

a == b: True
a is b: False
a is c: True
x is None: True
True False

The last line shows a CPython implementation detail: integers from -5 to 256 are cached, so two of them are the same object, while larger ones created at runtime usually are not. Code must never rely on that.

When == and is disagree

Because == runs code from the object's class, the two operators can give opposite answers even for None, and floating-point NaN is not equal to itself:

bash
cat > eq.py <<'EOF'
class Always:
    def __eq__(self, other):
        return True

obj = Always()
print("obj == None:", obj == None)
print("obj is None:", obj is None)

nan = float("nan")
print("nan == nan:", nan == nan, " nan is nan:", nan is nan)
print("nan in [nan]:", nan in [nan])

built = "".join(["err", "codex"])
literal = "errcodex"
print("built == literal:", built == literal, " built is literal:", built is literal)
EOF
python eq.py

Output

obj == None: True
obj is None: False
nan == nan: False  nan is nan: True
nan in [nan]: True
built == literal: True  built is literal: False
  • obj == None is True because Always.__eq__ says so, which is exactly why PEP 8 asks for is None.
  • nan == nan is False by the IEEE 754 rules, yet nan in [nan] is True: container membership tests (in, list.index, list.count) check identity first and only then call ==.
  • A string built at runtime equals the literal but is a different object, so is fails where == works.

Criterion by criterion

Criterionis==
ComparesObject identity (same id)Values, through __eq__
Can be overridden by a classNoYes
None, True, FalseRecommended (PEP 8)Works, but a class can make it lie
Numbers and stringsWrong: depends on caching and interningCorrect
SpeedSlightly faster (no method call)Calls __eq__

Python warns about is with a literal

bash
python -c 'x = 1000; print(x is 1000)'

Output

<string>:1: SyntaxWarning: "is" with 'int' literal. Did you mean "=="?
True

Since Python 3.8, is against a literal emits a SyntaxWarning that suggests ==.

Which one to use

Use == (and !=) whenever you care about the data: numbers, strings, lists, dicts, dataclasses, anything a user typed or a file contained. Use is (and is not) only when you need to know that a name refers to one particular object: None, NotImplemented, Ellipsis, enum members, and private sentinel objects such as _missing = object() used as a default argument. A sentinel works with is precisely because nothing else can be identical to it, even an object that compares equal.

A missing is None check is the usual cause of AttributeError: 'NoneType' object has no attribute: test for None before using a result that may be absent.

FAQ

Why does PEP 8 say to write x is None instead of x == None?

None is a singleton, so identity is the exact test. == calls the object's __eq__ method, which a class can override to return True for anything, so x == None can give the wrong answer.

Is x is not None the same as not x is None?

Yes, they give the same result. PEP 8 recommends is not because it reads as one operator and avoids confusion with not applied to x.

Why does 'abc' is 'abc' sometimes print True?

CPython may reuse one object for identical string constants (interning), so the identity test happens to succeed. It is an implementation detail; strings built at runtime are usually different objects, so compare text with ==.

Sources