Python id(): Discovering Object Memory Identity & CPython Lifetimes

The id() built-in function returns the unique integer identity of an object, corresponding to its actual memory address in CPython.

Identity vs. Value Equality: While the equality operator (==) checks whether two objects have matching values, the identity operator (is) evaluates whether two references point to the exact same memory address (id(a) == id(b)). To compute content-based integer hashes for dictionary keys, see hash(). For object type introspection, see type(), or check class hierarchies with isinstance().

1. Syntax & Formal Specifications 🔝

The id() function accepts a single object and returns its unique identity integer:

# Formal syntax of built-in id()
memory_identity = id(object)
Parameter Type Default Description & Operational Rule
object Any Required The target object whose unique memory identity is retrieved.
Return int N/A A unique integer guaranteed to be constant for the object's entire lifetime.
CPython Implementation Memory Pointer N/A Directly reflects the memory address of the C-level PyObject structure.

2. Memory Inspection & Hexadecimal Formatting 🔝

In CPython, wrapping id() in hex() reveals the standard hexadecimal memory address:

# 1. Inspecting memory identity and address formatting
user_score = 100
user_alias = user_score

print("id(user_score):     ", id(user_score))
print("id(user_alias):     ", id(user_alias))
print("Memory address (hex):", hex(id(user_score)))
print("Identity match (is): ", user_score is user_alias)

Terminal Output:

id(user_score):      140301581527624
id(user_alias):      140301581527624
Memory address (hex): 0x7f9a12e35a48
Identity match (is):  True

3. CPython Small Integer Caching (-5 to 256) 🔝

To optimize execution speed and reduce memory fragmentation, CPython pre-allocates an array of small integer singletons from -5 through 256 inclusive. Any integer in this range shares the exact same memory address across the entire runtime:

# 2. CPython small integer caching (-5 to 256)
x = 250
y = 250
print("Integers 250 in cache:   ", x is y, "| id(x) == id(y):", id(x) == id(y))

# Numbers outside the cache allocate distinct heap objects
val1 = 1000
val2 = int("1000")
print("Integers 1000 outside:   ", val1 is val2, "| val1 == val2:", val1 == val2)
print("Distinct memory addresses:", id(val1) != id(val2))

Terminal Output:

Integers 250 in cache:    True | id(x) == id(y): True
Integers 1000 outside:    False | val1 == val2: True
Distinct memory addresses: True

4. Mutability Impact: In-Place Modification vs. Reallocation 🔝

For mutable objects like lists, in-place element changes do not change the memory identity. For immutable types like tuples and strings, any change allocates a new object:

# 3. Mutable vs. immutable object identity under modification
# Mutable sequence (list): identity is preserved during in-place changes
nums = [1, 2, 3]
initial_list_id = id(nums)
nums.append(4)
nums[0] = 99
print("List ID unchanged: ", id(nums) == initial_list_id)

# Immutable sequence (tuple): modification creates a new object
coords = (10, 20)
initial_tuple_id = id(coords)
coords += (30, 40)
print("Tuple ID changed:   ", id(coords) != initial_tuple_id)

Terminal Output:

List ID unchanged:  True
Tuple ID changed:    True

5. Identity (is) vs. Value Equality (==) 🔝

Understanding the distinction between checking identity and comparing content values:

# 4. Identity comparison (is) vs. Equality comparison (==)
list_a = [10, 20, 30]
list_b = [10, 20, 30]

print("Value equality (list_a == list_b):   ", list_a == list_b)
print("Identity match (list_a is list_b):   ", list_a is list_b)
print("Memory IDs differ:                   ", id(list_a) != id(list_b))

# Aliasing points to the exact same memory address
list_c = list_a
print("Aliased identity (list_a is list_c): ", list_a is list_c)

Terminal Output:

Value equality (list_a == list_b):    True
Identity match (list_a is list_b):    False
Memory IDs differ:                    True
Aliased identity (list_a is list_c):  True

6. Non-Overlapping Lifetimes & Address Recycling 🔝

Object identity is only guaranteed unique among simultaneously existing objects. Once an object is garbage-collected, its memory address may be reassigned to a newly created object:

# 5. Non-overlapping lifetime and memory address recycling
def generate_temporary():
    temp = [1, 2, 3]
    return id(temp)

first_id = generate_temporary()
second_id = generate_temporary()

print("Address first temp:  ", hex(first_id))
print("Address second temp: ", hex(second_id))
print("Memory address reused:", first_id == second_id)

Terminal Output:

Address first temp:   0x7f9a12c41980
Address second temp:  0x7f9a12c41980
Memory address reused: True

7. Production Recipe: Graph Cycle Detector 🔝

When traversing complex cyclic data structures or object graphs, tracking visited memory IDs avoids infinite recursion loops:

# 6. Production Recipe: Graph Cycle Detector using Memory Identity
class GraphNode:
    def __init__(self, value):
        self.value = value
        self.neighbors = []

def has_cycle(start_node: GraphNode) -> bool:
    """Detects cycles in an arbitrary object graph by tracking visited memory IDs."""
    visited_ids = set()
    stack = [start_node]
    
    while stack:
        current = stack.pop()
        node_id = id(current)
        
        if node_id in visited_ids:
            return True  # Cycle detected
        visited_ids.add(node_id)
        
        for neighbor in current.neighbors:
            stack.append(neighbor)
    return False

n1 = GraphNode("Root")
n2 = GraphNode("Child_A")
n3 = GraphNode("Child_B")

n1.neighbors.append(n2)
n2.neighbors.append(n3)
print("Acyclic graph has cycle? ", has_cycle(n1))

# Create circular loop (n3 -> n1)
n3.neighbors.append(n1)
print("Circular graph has cycle?", has_cycle(n1))

Terminal Output:

Acyclic graph has cycle?  False
Circular graph has cycle? True

8. Common Developer Pitfalls & Traps 🔝

Never use is to compare numbers or string values. Always reserve is for comparing singletons like None:

# 7. Common traps: using 'is' with literal constants
# WARNING: Comparing non-singletons with 'is' triggers warnings and bugs
number = 1000
# if number is 1000:  <-- DON'T DO THIS! Use == instead
print("Correct equality check:     ", number == 1000)

# Singletons (None, True, False) MUST be compared with 'is'
empty_state = None
print("Correct singleton check (is):", empty_state is None)

Terminal Output:

Correct equality check:      True
Correct singleton check (is): True

9. Interactive Notebook & Practice Exercises 🔝

Practice memory inspection, integer caching boundaries, and cycle detection in our interactive companion Jupyter Notebook:

Interactive Companion Jupyter Notebook

Explore interactive code cells, benchmark memory allocation, inspect small integer caching, and run self-validating assertions.

View on GitHub Run in Google Colab

Self-Verifying Assertion Suite

Run these unit checks directly in Python or your local environment to verify id() mechanics:

# 8. Self-verifying unit test suite for id()
# Test 1: Identity returns integer
assert isinstance(id(object()), int)

# Test 2: Singleton identity guarantee
assert id(None) == id(None)
assert (None is None) is True

# Test 3: Small integer cache identity (-5 to 256)
a = 200
b = 200
assert a is b
assert id(a) == id(b)

# Test 4: Distinct mutable containers have distinct IDs
l1 = [1, 2]
l2 = [1, 2]
assert l1 == l2
assert l1 is not l2
assert id(l1) != id(l2)

# Test 5: In-place mutation maintains list identity
orig_id = id(l1)
l1.append(3)
assert id(l1) == orig_id

print("All id() unit assertions successfully verified!")

Terminal Output:

All id() unit assertions successfully verified!

10. Frequently Asked Questions (FAQ) 🔝

What does id() actually represent?

In CPython, id() returns the actual memory address where the object structure is stored in RAM.

Why do small integers have identical IDs?

CPython pre-allocates an internal cache of singleton integer objects from -5 through 256 for execution speed.

When should I use 'is' instead of '=='?

Use is only when comparing object identity against known singletons like None, True, or False.

Can two distinct objects share the same id()?

Yes, but only if their lifetimes do not overlap. If the first object is destroyed, its memory location can be reused by a new object.

Further Reading & Related Tutorials 🔝




Subscribe to our YouTube Channel here



plus2net.com







Python Video Tutorials
Python SQLite Video Tutorials
Python MySQL Video Tutorials
Python Tkinter Video Tutorials
✖
We use cookies to improve your browsing experience. . Learn more
HTML MySQL PHP JavaScript ASP Photoshop Articles Contact us
© 2000-2026 plus2net.com All rights reserved worldwide Privacy Policy Disclaimer