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 contentsBoth 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))The Integer Interning Trap
Python caches small integers (-5 to 256), making is comparisons appear to work but fail unpredictably:
x = 100
y = 100
print(x is y) # True (same cached object)
x = 1000
y = 1000
print(x is y) # False (different objects!)
print(x == y) # TrueCorrect Usage: Checking for None
The primary use case for is is checking for None, since None is a singleton:
result = some_function()
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__
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:
if flag is True:
print("Explicitly True")
if flag:
print("Truthy")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 itemsQuick 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.