Memory and References in Python
Lists are passed to functions by reference, meaning the function receives access to the original list, not a copy.
One List, Two Names
Suppose a caller has a list named letters and passes it to a function. Inside the function, the parameter is another name for access to that same list object. The function does not receive a separate copy. This is why an operation inside the function can change what the caller sees after the function returns.
Tracing a Deletion
What do you think happens?
Before the function delete_head is called, letters contains ['a', 'b', 'c']. The function deletes the element at index 0 through its parameter t. What does letters contain afterward?
Reveal answer
Answer: ['b', 'c']
The parameter t and the variable letters refer to the same list object. Deleting the element at index 0 changes that shared object, so the caller sees ['b', 'c'] after the function returns.
The delete_head example
A list named letters contains ['a', 'b', 'c']. The function delete_head receives that list through parameter t and removes the element at index 0.
Before the call: letters refers to the list containing ['a', 'b', 'c'].
During the call: The parameter t refers to the same list object. The deletion through t removes 'a' from that object.
After the call: letters still refers to the same object, which now contains ['b', 'c'].
The deletion made through the function parameter is visible through the caller's variable.
Reference Access
A value and a reference are different ideas in this explanation. A number is treated as a value: when a number is passed to a function and modified inside the function, the original number outside remains unchanged. A list is an object. When a list is passed to a function, Python passes a reference to that object rather than a copy of the list itself.
The important question is not which name you are using. The important question is whether an operation modifies the shared list object. If it does, the caller can see the change because both names refer to that object.
Mutation and Rebinding
| Operation | What happens to the list object | Effect visible to caller |
|---|---|---|
| Modify through the parameter | The shared list object changes | Yes |
| Reassign the parameter | The parameter refers to a different list | No |
Reassigning the parameter is different from modifying the list. If the function assigns the parameter to a new list such as [1, 2, 3], that changes what the parameter refers to, but it does not change the original list variable in the caller. Only modifications to the shared list object are visible to the caller.
Unexpected Changes
Unexpected list mutations often become easier to find when you trace every function call that received the list. Any of those functions could have modified the shared object. Follow the list from the caller into each parameter, then identify the operation that changed the object.
Assuming the function receives a copy of the list.
The function parameter and the caller's variable are aliases for the same list object.
Fix:
Check whether the function modifies the list object itself. Such a modification is visible to the caller.Treating parameter reassignment as if it were list mutation.
Reassigning the parameter changes the parameter's reference, not the caller's original list.
Fix:
Separate operations that modify the shared object from assignments that make the parameter refer to another list.Debugging only the caller and ignoring functions that received the list.
A function that receives the list can modify the original object through its parameter.
Fix:
Trace each call that passed the list and inspect the operations performed through the corresponding parameter.
Check Your Reasoning
A caller has a list named data. A function named remove_negatives receives data and deletes elements through its parameter numbers. Predict whether data can change after the function returns, then explain why.
Hints
- Ask whether numbers refers to the original list object or to a copy.
- Focus on whether the function deletes elements from the list object.
- Use the distinction between mutation and parameter reassignment.
The expected conclusion is that data can change. The function receives a reference to data's list object, and deleting elements through numbers modifies that actual object. When the function returns, the caller sees the modified list.
Remember the Path
- Passing a list to a function gives the function a reference to the original list rather than a copy.
- The caller's variable and the function parameter are aliases for the same list object.
- Mutating the list inside the function is visible to the caller.
- Reassigning the parameter to another list does not change the caller's original list.
- To debug an unexpected mutation, trace every function call that received the list and inspect how each function used its parameter.
Key Takeaways
- A list argument gives a function access to the original list object.
- The parameter inside the function and the caller's variable are aliases for that object.
- Changes made to the shared list are visible outside the function.
- Rebinding the parameter is not the same as modifying the shared list.
- Tracing references and mutations through function calls helps explain unexpected list changes.