What is the difference between != and is not in Python
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)
# Different 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
# Different objects in memory (even with same content)
print(list1 is not list2) # True (different objects)
# Same object in memory
print(list1 is not list3) # False (same object)
# Verify with id()
print(f"list1 id: {id(list1)}")
print(f"list2 id: {id(list2)}") # Different from list1
print(f"list3 id: {id(list3)}") # Same as list1
Output:
True
False
list1 id: 140077478709696
list2 id: 140077477528128
list3 id: 140077478709696
Visualizing the Difference
# Two separate objects with identical content
a = [1, 2, 3]
b = [1, 2, 3]
print(a != b) # False (content is equal)
print(a is not b) # True (different memory locations)
# Memory layout:
# a ──────► [1, 2, 3] (at memory address 0x1000)
# b ──────► [1, 2, 3] (at memory address 0x2000)
# Same object reference
c = a
print(a != c) # False (content is equal)
print(a is not c) # False (same memory location)
# Memory layout:
# a ──────┐
# ├──► [1, 2, 3] (at memory address 0x1000)
# c ──────┘
The None Special Case
Always use is not when checking for None:
def fetch_user(user_id):
# Returns None if user not found
users = {1: "Alice", 2: "Bob"}
return users.get(user_id)
result = fetch_user(999)
# ✅ Correct: use identity check for None
if result is not None:
print(f"Found: {result}")
else:
print("User not found")
# ❌ Avoid: works but not idiomatic
if result != None:
print(f"Found: {result}")
None is a singleton in Python-there's only one None object in memory. Using is not None is faster and more explicit about your intent.
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!)
# This is why 'is not None' is safer
if obj is not None:
print("Object exists") # This correctly executes
Output:
False
True
Object exists
Integer Caching Gotcha
Python caches small integers, which can cause confusing is behavior:
# Small integers are cached (-5 to 256)
a = 100
b = 100
print(a is not b) # False (same cached object!)
# Large integers are not cached
x = 1000
y = 1000
print(x is not y) # True (different objects)
# But values are still equal
print(x != y) # False (same value)
Output:
False
False
False
Never use is or is not to compare integers, strings, or other immutable values. The result depends on Python's internal caching and can vary between implementations.
String Interning
Similar caching behavior exists for strings:
# Simple strings may be interned
s1 = "hello"
s2 = "hello"
print(s1 is not s2) # False (same interned object)
# Strings with spaces typically aren't
s3 = "hello world"
s4 = "hello world"
print(s3 is not s4) # May be True or False!
# Always use != for string comparison
print(s3 != s4) # Reliable: False
Output:
False
False
False
Practical 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}")
# Note: 'if result:' would fail for index 0!
Output:
Found at index 1
Comparing Objects
user_input = [1, 2, 3]
expected = [1, 2, 3]
# Check if values differ
if user_input != expected:
print("Input doesn't match expected values")
else:
print("Values match!") # This prints
Output:
Values match!
Common Mistakes
# ❌ Wrong: Using 'is not' for value comparison
name1 = "Alice"
name2 = "Alice"
if name1 is not name2: # Unreliable!
print("Different names")
# ✅ Correct: Use != for value comparison
if name1 != name2:
print("Different names")
# ❌ Wrong: Using != for None check
value = None
if value != None: # Works but not Pythonic
process(value)
# ✅ Correct: Use 'is not' for None
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.