Skip to main content

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

OperatorChecksQuestion It Answers
!=Value/ContentDo these have different data?
is notIdentity/MemoryAre 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}")
tip

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
warning

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

ScenarioCorrect OperatorExample
Compare numbers!=if count != 0:
Compare strings!=if name != "admin":
Compare lists/dicts!=if data != expected:
Check for Noneis notif result is not None:
Check same objectis notif obj1 is not obj2:

Summary

  • Use != for comparing values-numbers, strings, lists, or any data content.
  • Use is not exclusively for checking None or 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.