Chapter 10 of 24 | Python
References, pointers, and memory
Python variables are names bound to objects. Assignment normally binds another name to the same object; it does not copy the object. This model explains many beginner surprises with lists and dictionaries.
original = ["a", "b"]
alias = original
alias.append("c")
print(original) # ['a', 'b', 'c']
Identity, equality, and copying
is checks identity; == checks equality according to an object's comparison behavior. Use identity for singletons such as None. To copy a list, use copy() or list(existing) ; these are shallow copies. A nested list can still contain shared inner objects.
import copy
matrix = [[1, 2], [3, 4]]
shallow = matrix.copy()
deep = copy.deepcopy(matrix)
matrix[0].append(99)
print(shallow[0]) # shares the inner list
print(deep[0]) # independent nested list
Deep copying is not always appropriate. Files, sockets, locks, caches, and objects with external identity may not have a meaningful duplicate. Define copying behavior deliberately for domain objects, or avoid copying and make ownership explicit.
Mutability and function calls
Python passes object references by assignment (sometimes informally called call-by-sharing). A function receives a local name bound to the same object passed by the caller. Mutating that object can be visible to the caller; rebinding the parameter name does not rebind the caller's name.
def add_note(notes):
notes.append("done") # mutates shared list
def replace_notes(notes):
notes = ["new"] # only rebinds local name
items = []
add_note(items)
replace_notes(items)
print(items) # ['done']
Memory model and object lifetime
Python's language model defines object behavior, not a universal memory layout. In CPython, reference counting helps reclaim many objects promptly and a cyclic garbage collector handles reference cycles. Other implementations may use different garbage-collection strategies. Do not rely on immediate finalization to release files or network connections: use context managers and explicit lifecycle methods.
A reference cycle can keep objects reachable from one another even when the application no longer needs them. Garbage collection can reclaim many cycles, but finalizers and external resources make cycles harder to reason about. Prefer clear ownership and context-managed resources.
Beginner-to-master progression
- Beginner: recognize that names can alias mutable values.
- Practitioner: choose shallow or deep copies intentionally and document mutation in APIs.
- Advanced: understand implementation-specific object layouts, reference counting, cyclic GC, weak references, and the cost of allocations without depending on CPython internals for portability.
Exercises
- Pass the same list to two functions: one mutates it and one rebinds its parameter. Predict each effect.
- Make a shallow copy of a nested dictionary and identify shared inner objects.
- Use
weakrefon a suitable user-defined object and explain how a weak reference differs from an owning reference.
Identity and object layout
id(obj) returns an identity value that is unique among simultaneously alive objects, but its exact representation is implementation-defined. CPython commonly uses a memory address, while other implementations need not. Do not persist an id or assume it exposes a portable address. Use is only when identity is the intended question.
Object sizes and layouts are implementation details. sys.getsizeof reports the shallow size of one object in CPython; it does not recursively include all objects referenced by a list or dictionary. Memory investigations need tools that account for object graphs, allocator arenas, and native allocations from extension modules.
Copy protocols and custom objects
The copy module can call user-defined hooks such as __copy__ and __deepcopy__. A class containing a lock, open file, cache, or unique identifier may need custom copying behavior or may need to reject copying. Deep copying can duplicate a large graph and may not preserve relationships with external resources.
Often the simplest policy is to make ownership explicit and provide a domain method such as clone_configuration() that copies only meaningful data. Naming the operation makes it clearer than implying every field can safely be duplicated.
Cycles, weak references, and retention
Reference cycles can be reclaimed by cyclic garbage collection in common implementations, but objects with finalizers and external resources need careful design. Weak references let caches and observers point to objects without extending their lifetime. A weak reference can become empty, so code must check it before use.
Many apparent memory leaks are retention bugs: a global list, callback closure, cache, or task still holds a reference. Use allocation tracing and object-reference inspection to identify what keeps the object alive, rather than assuming the garbage collector failed.
Mastery checkpoint
- Profile a nested data structure and distinguish shallow object size from retained graph size.
- Define a deliberate copy policy for a class that owns a cache and a lock.
- Find a retained-object path and fix the ownership or cache lifetime that keeps it reachable.