These two operators serve fundamentally different purposes: != compares whether values are different, while is not checks whether two references point to different objects in memory. Choosing the wrong one can lead to subtle bugs.
Core Differences
| Operator | Checks | Question It Answers |
|---|---|---|
!= |
Value/Content | Do these have different data? |
is not |
Identity/Memory | Are these different objects? |
Value Comparison with !=
The != operator compares the actual content of objects:
list1 = [1, 2, 3]
list2 = [1, 2, 3]
print(list1 != list2) # False (same content)
print(list1 == list2) # True (same content)
list3 = [1, 2, 4]
print(list1 != list3) # True (different content)How != Works
Python calls the __ne__ (not equal) method when you use !=:
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
def __eq__(self, other):
if not isinstance(other, Product):
return NotImplemented
return self.name == other.name and self.price == other.price
def __ne__(self, other):
return not self.__eq__(other)
product1 = Product("Widget", 9.99)
product2 = Product("Widget", 9.99)
product3 = Product("Gadget", 19.99)
print(product1 != product2) # False (same values)
print(product1 != product3) # True (different values)Identity Comparison with is not
The is not operator checks whether two references point to different memory locations:
list1 = [1, 2, 3]
list2 = [1, 2, 3]
list3 = list1 # Same reference
print(list1 is not list2) # True (different objects)
print(list1 is not list3) # False (same object)
print(f"list1 id: {id(list1)}")
print(f"list2 id: {id(list2)}") # Different from list1
print(f"list3 id: {id(list3)}") # Same as list1Output:
True
False
list1 id: 140077478709696
list2 id: 140077477528128
list3 id: 140077478709696Visualizing the Difference
a = [1, 2, 3]
b = [1, 2, 3]
print(a != b) # False (content is equal)
print(a is not b) # True (different memory locations)
c = a
print(a != c) # False (content is equal)
print(a is not c) # False (same memory location)The None Special Case
Always use is not when checking for None:
def fetch_user(user_id):
users = {1: "Alice", 2: "Bob"}
return users.get(user_id)
result = fetch_user(999)
if result is not None:
print(f"Found: {result}")
else:
print("User not found")
if result != None:
print(f"Found: {result}")Why is not None Matters
Some objects can incorrectly compare equal to None:
class TrickyObject:
def __eq__(self, other):
return True # Equals everything!
obj = TrickyObject()
print(obj != None) # False (comparison says they're "equal")
print(obj is not None) # True (but they're different objects!)
if obj is not None:
print("Object exists") # This correctly executesOutput:
False
True
Object existsInteger Caching Gotcha
Python caches small integers, which can cause confusing is behavior:
a = 100
b = 100
print(a is not b) # False (same cached object!)
x = 1000
y = 1000
print(x is not y) # True (different objects)
print(x != y) # False (same value)Output:
False
False
FalseString Interning
Similar caching behavior exists for strings:
s1 = "hello"
s2 = "hello"
print(s1 is not s2) # False (same interned object)
s3 = "hello world"
s4 = "hello world"
print(s3 is not s4) # May be True or False!
print(s3 != s4) # Reliable: FalseOutput:
False
False
FalsePractical Examples
Checking Optional Parameters
def greet(name=None):
if name is not None:
print(f"Hello, {name}!")
else:
print("Hello, stranger!")
greet("Alice") # Hello, Alice!
greet() # Hello, stranger!Output:
Hello, Alice!
Hello, stranger!Validating Return Values
def find_index(items, target):
"""Return index of target, or None if not found."""
for i, item in enumerate(items):
if item == target:
return i
return None
result = find_index([1, 2, 3], 2)
if result is not None:
print(f"Found at index {result}")Output:
Found at index 1Comparing Objects
user_input = [1, 2, 3]
expected = [1, 2, 3]
if user_input != expected:
print("Input doesn't match expected values")
else:
print("Values match!") # This printsOutput:
Values match!Common Mistakes
name1 = "Alice"
name2 = "Alice"
if name1 is not name2: # Unreliable!
print("Different names")
if name1 != name2:
print("Different names")
value = None
if value != None: # Works but not Pythonic
process(value)
if value is not None:
process(value)Quick Reference
| Scenario | Correct Operator | Example |
|---|---|---|
| Compare numbers | != |
if count != 0: |
| Compare strings | != |
if name != "admin": |
| Compare lists/dicts | != |
if data != expected: |
| Check for None | is not |
if result is not None: |
| Check same object | is not |
if obj1 is not obj2: |
Summary
- Use
!=for comparing values-numbers, strings, lists, or any data content. - Use
is notexclusively for checkingNoneor when you specifically need to verify that two variables reference different objects in memory.
Following this rule prevents subtle bugs and keeps your code Pythonic.