What is the difference between == and is operators
These two operators test fundamentally different things. Confusing them leads to subtle bugs that may only appear in certain conditions.
Equality with == (Value Comparison)
The == operator checks if two objects have the same value. It calls the object's __eq__ method internally.
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True - same contents
Both lists contain identical elements, so they're equal even though they're separate objects.
Identity with is (Object Comparison)
The is operator checks if two references point to the exact same object in memory. It compares id() values.
a = [1, 2, 3]
b = [1, 2, 3]
c = a # c references the same object as a
print(a is b) # False - different objects
print(a is c) # True - same object
print(id(a), id(b), id(c))
# 140234866534720 140234866535040 140234866534720
The Integer Interning Trap
Python caches small integers (-5 to 256), making is comparisons appear to work but fail unpredictably:
# Small integers are cached
x = 100
y = 100
print(x is y) # True (same cached object)
# Larger integers are not
x = 1000
y = 1000
print(x is y) # False (different objects!)
# But they're still equal
print(x == y) # True
Never use is to compare numbers or strings. The result depends on Python's internal caching behavior, which varies between implementations and versions.
# ❌ Unreliable
if user_count is 0:
print("No users")
# ✅ Correct
if user_count == 0:
print("No users")
Correct Usage: Checking for None
The primary use case for is is checking for None, since None is a singleton:
result = some_function()
# ✅ Correct - None is a singleton
if result is None:
print("No result")
if result is not None:
print(f"Got: {result}")
This is preferred over == None because:
- It's faster (no method call)
- It's explicit about checking identity
- It avoids issues with objects that override
__eq__
# A class with unusual equality
class Quirky:
def __eq__(self, other):
return True # Equals everything!
q = Quirky()
print(q == None) # True (misleading!)
print(q is None) # False (correct)
Checking Boolean Singletons
Similarly, True and False are singletons:
# ✅ Acceptable for explicit boolean check
if flag is True:
print("Explicitly True")
# ✅ More common and Pythonic
if flag:
print("Truthy")
In practice, prefer truthiness checks (if flag:) over identity checks (if flag is True:). The identity check fails for truthy non-boolean values:
value = 1
print(value is True) # False
print(bool(value)) # True
Mutable Default Argument Gotcha
Understanding is helps explain a common Python pitfall:
def append_item(item, items=[]):
items.append(item)
return items
a = append_item(1)
b = append_item(2)
print(a) # [1, 2] - unexpected!
print(b) # [1, 2]
print(a is b) # True - same object!
The default list is created once and reused. Fix with:
def append_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
Quick Reference
| Scenario | Operator | Example |
|---|---|---|
| Compare values | == | if x == 10: |
| Compare strings | == | if name == "Alice": |
| Compare lists/dicts | == | if data == expected: |
| Check for None | is | if result is None: |
| Check not None | is not | if result is not None: |
Summary
Use == for value comparisons-it's the right choice 99% of the time. Reserve is for identity checks, primarily when comparing against None. Never use is with integers, strings, or other values where Python's caching behavior could cause inconsistent results.