id() Function Complexity¶
The id() function returns a unique identifier for an object. In CPython, this is the object's memory address, but the exact meaning depends on the Python implementation.
Complexity Reference¶
| Operation | Time | Space | Notes |
|---|---|---|---|
id(object) |
O(1) | O(1) | Get object identity (memory address in CPython) |
| ID comparison | O(1) | O(1) | Compare two IDs (integer comparison) |
is operator |
O(1) | O(1) | Equivalent to id(a) == id(b) but faster |
Basic Usage¶
Getting Object Identity¶
# Get unique ID - O(1)
x = 42
id_x = id(x) # O(1) - unique number
y = 42
id_y = id(y) # O(1)
# Same value doesn't mean same object
print(x == y) # True - equal values
print(id(x) == id(y)) # Might be True (for small ints) or False
# Different objects always have different IDs
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b) # True - equal content
print(id(a) == id(b)) # False - different objects
Object Identity¶
# is operator uses id() comparison - O(1)
x = [1, 2, 3]
y = x # Same reference
print(x is y) # True - same object
print(id(x) == id(y)) # True - same ID
z = [1, 2, 3]
print(x is z) # False - different objects
print(id(x) == id(z)) # False - different IDs
Understanding Python Objects¶
Immutable Types¶
# Small integers are cached - same ID
a = 5
b = 5
print(id(a) == id(b)) # True - same cached object
# Larger integers are not cached
c = 256
d = 256
print(id(c) == id(d)) # True - boundary case
e = 257
f = 257
print(id(e) == id(f)) # False - different objects!
# String interning (same for equal strings sometimes)
s1 = "hello"
s2 = "hello"
print(id(s1) == id(s2)) # Usually True (interned)
t1 = "".join(['h', 'e', 'l', 'l', 'o'])
t2 = "".join(['h', 'e', 'l', 'l', 'o'])
print(id(t1) == id(t2)) # False (not interned)
Mutable Types¶
# Lists create new objects - O(1) to get ID
lst1 = [1, 2, 3]
lst2 = [1, 2, 3]
print(id(lst1) == id(lst2)) # False - different objects
# Assignment creates reference, not copy
lst3 = lst1
print(id(lst1) == id(lst3)) # True - same object
print(lst1 is lst3) # True
# Modification affects both
lst3.append(4)
print(lst1) # [1, 2, 3, 4] - both modified
Common Patterns¶
Checking Object Identity¶
# Use is/is not instead of id() directly - cleaner
x = [1, 2, 3]
y = x
z = [1, 2, 3]
# Good - use is operator
if x is y: # O(1) - equivalent to id(x) == id(y)
print("Same object")
if x is not z: # O(1)
print("Different objects")
# Avoid - using id() directly (less readable)
if id(x) == id(y):
print("Same object")
Tracking Object Lifecycle¶
# Track when objects are created and destroyed
import weakref
objects = {}
class MyObject:
def __init__(self, name):
self.name = name
# Store weak reference to track object
obj_id = id(self) # O(1)
objects[obj_id] = self.name
print(f"Created: {self.name} (ID: {obj_id})")
def __del__(self):
obj_id = id(self) # O(1)
if obj_id in objects:
del objects[obj_id]
print(f"Destroyed: {objects.get(obj_id, 'unknown')}")
# Usage
obj = MyObject("test") # Created with ID
del obj # Destroyed
Cycle Detection¶
# Detect cycles in object graph - O(n) with id()
def has_cycle(obj, visited=None):
"""Check if object has reference cycle"""
if visited is None:
visited = set()
obj_id = id(obj) # O(1)
if obj_id in visited:
return True # Cycle detected!
visited.add(obj_id) # O(1)
# Check attributes (simplified)
if hasattr(obj, '__dict__'):
for attr_val in obj.__dict__.values():
if has_cycle(attr_val, visited):
return True
return False
# Usage
a = {}
b = {'a': a}
a['b'] = b # Create cycle
print(has_cycle(a)) # True - cycle found
ID vs Equality¶
Difference: == vs is¶
# == checks value equality
x = [1, 2, 3]
y = [1, 2, 3]
print(x == y) # True - same content
# is checks object identity
print(x is y) # False - different objects
print(id(x) == id(y)) # False - equivalent
# For immutables, often same
a = (1, 2, 3)
b = (1, 2, 3)
print(a == b) # True
print(a is b) # Might be True or False (optimization)
# For None, always same object
c = None
d = None
print(c is d) # Always True
print(id(c) == id(d)) # Always True
Hashing and ID¶
# Hash and ID are different
x = 42
print(hash(x)) # Hash value - O(1)
print(id(x)) # Object ID - O(1)
# Different objects can have same hash
# But will have different IDs
y = [1, 2, 3]
z = [1, 2, 3]
# hash(y) raises TypeError (unhashable)
# But id(y) != id(z)
Implementation Details¶
CPython: ID is Memory Address¶
# In CPython, id() returns memory address - O(1)
x = object()
memory_addr = id(x) # O(1) - memory address
# This is why small integers have same ID
# They're cached in memory
# ID persists as long as object exists
print(id(x)) # Same memory address
print(id(x)) # Same memory address again
Other Implementations¶
# PyPy, Jython, IronPython may have different IDs
# But id() still O(1)
# ID not guaranteed to be memory address
# Just guaranteed to be unique per object
# id() is mainly for debugging
# Don't rely on specific ID values
Performance Considerations¶
ID Lookup Speed¶
# id() is very fast - O(1)
import time
x = object()
start = time.time()
for _ in range(1000000):
id(x) # O(1) each
end = time.time()
print(f"1M calls: {end - start:.4f}s") # Very fast!
Using id() in Collections¶
# Can use object ID as dict key - O(1) lookup
cache = {}
obj = object()
obj_id = id(obj) # O(1)
cache[obj_id] = "data" # O(1)
value = cache[obj_id] # O(1)
# But prefer using object itself as key
cache = {}
cache[obj] = "data" # O(1) - cleaner
value = cache[obj] # O(1)
Debugging and Inspection¶
Object Tracking¶
# Use id() to track object creation - O(1)
import sys
def track_objects():
"""Show all objects and their IDs"""
objects = {}
# Get all objects (memory intensive!)
for obj in gc.get_objects(): # O(n)
obj_id = id(obj) # O(1)
obj_type = type(obj).__name__
objects[obj_id] = obj_type
return objects
# Usage - useful for debugging memory issues
# import gc
# tracked = track_objects()
Identity and Memory¶
# id() shows object placement in memory
x = "hello"
y = "hello"
z = x
print(id(x)) # Memory address 1
print(id(y)) # Memory address 2 or 1 (interned)
print(id(z)) # Memory address 1 (same as x)
# Shows that z and x are same object
print(x is z) # True
Special Cases¶
None and Booleans¶
# None is singleton - always same object
a = None
b = None
print(id(a) == id(b)) # Always True
print(a is b) # Always True
# Booleans are singletons
true1 = True
true2 = True
print(id(true1) == id(true2)) # Always True
false1 = False
false2 = False
print(id(false1) == id(false2)) # Always True
Version Notes¶
- Python 2.x: id() available, returns memory address in CPython
- Python 3.x: Same behavior as Python 2
- All implementations: id() guaranteed O(1)
Related Functions¶
- hash() - Different from ID; used for dict/set
- isinstance() - Check type, not identity
- type() - Get object type
Best Practices¶
✅ Do:
- Use
isoperator instead ofid(x) == id(y) - Use
is Nonefor None checks (standard) - Use id() for debugging and object tracking
- Remember ID is implementation-dependent
❌ Avoid:
- Using id() in regular code (use
isinstead) - Assuming specific ID values
- Relying on ID across Python sessions
- Using ID as hash for collections (use hash() or object itself)
- Comparing IDs between different Python implementations