List Methods: append, extend, pop, and del
Reassigning a function parameter (e.g., t = t[1:]) does not modify the original list passed to the function; it only changes what the local variable refers to.
The List Object at Stake
When a list is passed to a function, the function can either change the existing list object or make its local parameter refer to a different list. These actions look similar when you read the function, but they have different effects on the caller's list. The central question is not simply which list operation was written. It is whether the existing list was modified or whether a new list was produced.
Methods such as append() and pop() modify the actual list object in place, so the change is visible to the caller. Slicing and the + operator create new lists, leaving the original list unchanged.
Four Ways to Change List Contents
| Operation | Effect on the list | Resulting purpose |
|---|---|---|
| append() | Modifies the existing list in place | Add one item to the list |
| extend() | Modifies the existing list in place | Add each item from another iterable |
| pop() | Modifies the existing list in place | Remove an element and return it |
| del | Removes an element from the list | Remove an element without returning it |
| Slicing | Creates a new list | Produce a list based on part of another list |
| + | Creates a new list | Combine lists into a separate list |
The key distinction is whether the existing list is changed or a new list is produced.
append() and extend() both add contents in place, but they add contents at different levels. append() adds one item as a single new element. If that item is itself a list, the nested list remains one element. extend() takes the items from another iterable and adds those items individually. Thus, choosing between them depends on whether the incoming object should remain one item or be unpacked into several items.
pop() and del both remove list elements. pop() is useful when the removed element is also needed, because it removes the element and returns it. del removes the selected element without returning it. Both are removal operations, but their function purposes differ: one produces a removed value for further use, while the other simply performs the deletion.
Why Parameter Reassignment Stops at the Function
Consider a function whose parameter is named t. At the beginning of the call, t refers to the list supplied by the caller. If the function evaluates t[1:], slicing creates a new list. Reassigning t to that slice changes what the local name t refers to, but it does not replace or edit the caller's original list. When the function finishes, the caller's list is still the original object.
Tracing a Slice Reassignment
A caller supplies the list [10, 20, 30] to a function. Inside the function, the parameter is reassigned to the slice beginning at index 1. What list remains visible to the caller?
Initial reference: The function parameter refers to the caller's original list, whose contents are [10, 20, 30].
Slice creation: The slice beginning at index 1 produces a new list containing [20, 30].
Local reassignment: The parameter is made to refer to the new slice list. This changes the local parameter reference, not the original list.
After the function: The caller still has the original list because no in-place list method changed it.
The caller sees [10, 20, 30]. The function's local parameter refers to [20, 30] only after reassignment.
Choosing a Function Contract
A function that is intended to change the caller's list should use an in-place operation such as append(), extend(), or pop(). Because these methods modify the actual list object, the caller can observe the change after the function call. A function that is intended to preserve the input should use an operation such as slicing or + to create a new list and then return that new list.
| Function intention | Suitable approach | Caller’s original list |
|---|---|---|
| Change the supplied list | Use an in-place method | Changed and visible after the call |
| Produce a revised list | Use slicing or + | Left unchanged |
| Remove and use a value | Use pop() | Changed; removed value is returned |
| Remove without needing a value | Use del | Changed; no removed value is returned |
The function's intended contract should determine whether it mutates the input or returns a separate list.
Choose one clear purpose for each list-processing function. If the purpose is to modify the supplied list, make that intent clear through the function's naming and documentation. If the purpose is to return a revised list while preserving the input, state that intent clearly as well.
Common List-Operation Mistakes
Assuming that assigning a slice back to a function parameter changes the caller's list
Slicing creates a new list, and parameter reassignment changes only what the local parameter refers to.
Fix:
Use an in-place list operation when the function must change the list supplied by the caller, or return the new sliced list when the original should remain unchanged.Using append() when the incoming list should be added item by item
append() adds one item, even when that item is itself a list.
Fix:
Use extend() when the items from another iterable should be added individually.Using del when the removed value is needed later
del removes the element without returning it.
Fix:
Use pop() when removal and retrieval of the removed element are both required.Changing a list in place when the function should preserve the input
In-place methods modify the actual list object and make the change visible to the caller.
Fix:
Use slicing or + to create a new list, and return that new list.
Practice the Contract
Design two functions for a list of task names. The first function should add a new task to the supplied list so that the caller's list changes. The second function should create a list containing all tasks except the first one while leaving the caller's original list unchanged.
Hints
- For the first function, choose an in-place method.
- For the second function, use slicing and return the resulting list.
- After each function call, compare the caller's original list with the value produced by the function.
What do you think happens?
A list contains [red, blue]. A function uses an in-place operation to add green, while another function creates a slice and returns it. Which function changes the caller's original list?
Reveal answer
Answer: Only the function using the in-place operation
An in-place operation modifies the actual list object and the change is visible to the caller. A slice creates a new list, so returning it does not modify the original list.
A Reliable Decision Rule
- Decide whether the function should change the supplied list or preserve it.
- For a visible change to the caller's list, use an in-place operation such as append(), extend(), pop(), or del.
- For a separate revised list, use slicing or + and return the new list.
- Use append() for one added item and extend() for adding items individually from another iterable.
- Use pop() when removal and the removed value are both needed; use del when removal is needed without a returned value.
- Make the function's mutation or return behavior clear through its naming and documentation.
Key Takeaways
- In-place methods modify the existing list object, so their changes are visible to the caller.
- append() adds one item, while extend() adds items individually from another iterable.
- pop() removes and returns an element; del removes an element without returning it.
- Slicing and the + operator create new lists and leave the original list unchanged.
- Reassigning a function parameter to a slice changes only the local parameter reference, not the caller's list.