Quick summary
Summarize this blog with AI
Use == to ask whether two values compare equal. Use is to ask whether two names refer to the exact same object. Equal values do not generally imply shared identity. Identity checks are useful for None, explicit sentinels, and intentional aliasing; they are not a shortcut for comparing numbers or strings.
These examples use Python 3.12.14 and the standard library. Run the blocks in order. None relies on a particular integer cache or compiler constant-folding decision.
Equal lists can be different objects
first = [10, 20]
second = [10, 20]
alias = first
print(first == second)
print(first is second)
print(first is alias)
alias.append(30)
print(first)
print(second)
Expected output:
True
False
True
[10, 20, 30]
[10, 20]
The two list expressions construct distinct lists. Assignment to alias does not copy the list: it binds another name to the same object. Appending through that name changes the shared object. Equality compares list contents; identity reveals whether a mutation would reach the same list.
A shallow copy separates only the outer container
import copy
original = [[1], [2]]
shallow = original.copy()
deep = copy.deepcopy(original)
print(original is shallow)
print(original[0] is shallow[0])
shallow[0].append(9)
print(original)
print(deep)
Expected output:
False
True
[[1, 9], [2]]
[[1], [2]]
The shallow copy has a new outer list but retains references to the nested lists. A deep copy recursively copies this fixture's nested values. That is a copying decision, not a reason to replace value comparisons with identity tests. A tuple can also hold a mutable object, so an immutable outer container does not make its entire object graph immutable.
Check None and a private sentinel by identity
MISSING = object()
settings = {'limit': None, 'enabled': False, 'count': 0}
for key in ('limit', 'absent', 'enabled', 'count'):
value = settings.get(key, MISSING)
if value is MISSING:
state = 'missing'
elif value is None:
state = 'explicit None'
else:
state = repr(value)
print(key, state)
Expected output:
limit explicit None
absent missing
enabled False
count 0
One sentinel instance distinguishes an absent key from a key explicitly set to None. Recreating object() inside the comparison would create a different sentinel. A truth test such as if not value would also match zero, False, and empty containers, so it cannot express the same four-way distinction.
Equality belongs to the object's type
from dataclasses import dataclass
@dataclass(frozen=True)
class Product:
sku: str
left = Product('A-10')
right = Product('A-10')
print(left == right)
print(left is right)
print(left == 'A-10')
Expected output:
True
False
False
The dataclass supplies field-based equality, so separate Product instances can compare equal. For a hand-written __eq__, return NotImplemented for unsupported comparison types so Python can follow its comparison protocol. Equality can be customized; identity cannot be overloaded this way. Some libraries return an array of comparisons from == rather than a single boolean, so use their documented aggregate comparison when checking entire arrays or tables.
Do not depend on literal interning
An interpreter may reuse some immutable objects. Results from a is b for equal integer or string values can differ between interactive input, a compiled script, optimization levels, and implementations. The size of an integer cache is not a language-level value-comparison contract. Likewise, separately incrementing numbers around 256 is not a portable way to demonstrate identity.
If you intentionally use string interning, call its explicit interface and keep the returned objects. The following result follows from that interface, rather than guessing which literal the compiler will reuse.
import sys
one = sys.intern(''.join(['product', '_code']))
two = sys.intern('product_code')
print(one == two)
print(one is two)
Expected output:
True
True
This does not make interning a default performance recommendation. Measure a real workload before adding it, and continue using == for ordinary text comparisons. Numeric values should also use equality even when an identity test happens to pass in one session.
Choose the question before the operator
For a value comparison, use == or the library's documented equivalent. For “is this the same object?” use is. For “was this optional value supplied?” use a stable sentinel or is None according to the input contract. Test aliasing with independently constructed mutable objects so the lesson remains valid across interpreters.
See dictionary keys and equality for the related hash contract. Primary references: Python's identity comparisons, copying operations, and explicit string interning.