Python Essentials for AI Engineers · Lesson 10 of 36
What is the difference between == and is?
The Core Difference
==compares values (calls__eq__)iscompares identity — whether two variables point to the exact same object in memory
a = [1, 2, 3]
b = [1, 2, 3] # Same values, different objects
c = a # Same object
print(a == b) # True — same values
print(a is b) # False — different objects in memory
print(a is c) # True — c is the same object as a
# Verify with id()
print(id(a)) # e.g., 140234567890
print(id(b)) # Different number
print(id(c)) # Same number as id(a)is for None Checks
The most important use of is in Python: always check for None with is, not ==:
result = None
# Correct
if result is None:
print("No result yet")
if result is not None:
process(result)
# Works but not idiomatic — PEP 8 recommends `is None`
if result == None:
print("No result yet")
# Why `is`? Because a custom class can override __eq__ to make == None return True
class ConfusingClass:
def __eq__(self, other):
return True # Equal to everything, including None!
obj = ConfusingClass()
print(obj == None) # True — unexpected
print(obj is None) # False — correct: obj is not Noneis for Boolean Checks
Similarly, use is to check True and False identity explicitly (though usually just if flag: is preferred):
flag = True
# Idiomatic — check truthiness
if flag:
print("Flag is truthy")
# Identity check — stricter
if flag is True:
print("Flag is exactly True, not just truthy")
# Difference: 1 == True but 1 is not True
print(1 == True) # True — int 1 equals bool True (True is a subclass of int)
print(1 is True) # False — different objects
print(0 == False) # True
print(0 is False) # FalseInteger Caching (CPython Implementation Detail)
CPython caches small integers (-5 to 256) — they're always the same object:
a = 100
b = 100
print(a is b) # True — cached, same object
a = 1000
b = 1000
print(a is b) # False — large ints are different objects
# (May be True in some contexts due to compiler optimizations)Do not rely on integer caching — it's an implementation detail of CPython, not a language guarantee. Never use is to compare integers.
String Interning
Python automatically interns short strings that look like identifiers (no spaces, no special characters). Interned strings share the same object:
a = "warfarin"
b = "warfarin"
print(a is b) # True — interned (simple identifier-like string)
a = "warfarin aspirin interaction"
b = "warfarin aspirin interaction"
print(a is b) # Probably False — not interned (spaces present)
# Force interning with sys.intern
import sys
a = sys.intern("warfarin aspirin interaction")
b = sys.intern("warfarin aspirin interaction")
print(a is b) # True — now internedNever use is to compare strings — interning behavior is an implementation detail, not a contract.
== and Custom Classes
== calls __eq__. You can define what equality means for your class:
from dataclasses import dataclass
@dataclass
class Drug:
name: str
dose_mg: float
# @dataclass automatically generates __eq__ based on fields
warfarin_a = Drug("warfarin", 5.0)
warfarin_b = Drug("warfarin", 5.0)
warfarin_c = Drug("warfarin", 10.0)
print(warfarin_a == warfarin_b) # True — same name and dose
print(warfarin_a == warfarin_c) # False — different dose
print(warfarin_a is warfarin_b) # False — different objects
# Custom __eq__ without @dataclass
class DrugManual:
def __init__(self, name: str, dose_mg: float):
self.name = name
self.dose_mg = dose_mg
def __eq__(self, other):
if not isinstance(other, DrugManual):
return NotImplemented
return self.name.lower() == other.name.lower() and self.dose_mg == other.dose_mg
def __hash__(self):
return hash((self.name.lower(), self.dose_mg))
# If you define __eq__, you must define __hash__ if you want the class to be hashableCommon Bugs
# Bug 1: Using == None instead of is None
# Fragile — breaks with classes that override __eq__
if some_result == None: # Use is None instead
...
# Bug 2: Using is for value comparison
x = 1000
y = 1000
if x is y: # WRONG — False for large ints even if equal
print("Equal") # Won't print despite x == y being True
# Use == for value comparison
if x == y: # Correct
print("Equal")
# Bug 3: Checking mutable container emptiness with is
my_list = []
if my_list is None: # WRONG — empty list is not None
print("No data")
if not my_list: # Correct — empty list is falsy
print("No data")
if my_list is not None and len(my_list) == 0: # Also correct, more explicit
print("No data")When to Use Each
| Situation | Use |
|---|---|
| Compare values (numbers, strings, lists) | == |
| Check for None | is None / is not None |
| Check for True / False (explicitly) | is True / is False |
| Check if two variables point to same object | is |
| Compare custom objects | == (define __eq__) |
| Compare strings | == (never is) |
| Compare integers | == (never is) |
Quick Reference
# Always correct
result is None
result is not None
value == 42
text == "warfarin"
list_a == list_b
# Correct for identity checks only
a is b # Same object check
obj is True # Exactly True (not just truthy)
# Never do these
value is 42 # Unreliable for ints > 256
text is "warfarin" # Unreliable for strings
result == None # Use is None instead