Debugging List Programs and Avoiding Common Pitfalls
Aliasing means two variables point to the same list object, so changes through one variable affect the other.
The Unexpected Change
A list can appear to change in a part of a program that you did not expect. A common reason is aliasing: two variables point to the same list object. The variables have different names, but they still provide access to one shared list. Therefore, changing the list through one variable affects what the other variable shows.
What do you think happens?
Suppose two variables refer to the same list and the list is changed through one variable. What should you expect from the other variable?
Reveal answer
It shows the change because both variables refer to the same list object
Aliasing means that two variables point to one shared list object. A change made through either variable affects that shared object.
Tracing Shared References
t = [3, 1, 2]
orig = t
t.append(4)
print(t)
print(orig)- 1The first line creates a list and associates it with t.
- 2The second line gives the same list object another variable name, orig.
- 3The third line mutates the list through t.
- 4The two print statements show the changed shared list through both names.
- t starts as a list.
- orig receives another reference to that same list object.
- The mutation through t changes the shared list.
- Both names display the changed contents.
[3, 1, 2, 4]
[3, 1, 2, 4]When debugging, trace every variable that may refer to a list. If a mutation made through one name becomes visible through another name, the names are acting as aliases for the same list object. This observation is different from merely seeing equal contents: the important question is whether a later change made through one variable is also visible through the other.
Slicing for Safety
When the original list must be preserved, create a defensive copy with orig = t[:]. The slice creates an independent list that can be modified safely. The new variable can then be changed without making it an alias for the original list.
t = [3, 1, 2]
orig = t[:]
t.append(4)
print(t)
print(orig)- 1The first line creates the list associated with t.
- 2The slice expression t[:] is used to create the defensive copy.
- 3Appending through t changes t's list.
- 4orig retains the contents of its independent list.
- t starts as the original list.
- orig = t[:] creates an independent list.
- The mutation is made through t.
- The original contents held by orig remain unchanged.
[3, 1, 2, 4]
[3, 1, 2]orig = torig and t refer to the same list object.
orig = t[:]orig is an independent list that can be modified safely.
The defensive-copy pattern is useful when one version of the data must remain available while another version is changed. The essential difference is not the spelling of the variable names; it is whether the names refer to one shared list or to separate lists.
Sorting Without Losing Data
| Choice | Effect on original list | Result |
|---|---|---|
| sorted(t) | Does not modify the original | A new sorted list |
| t.sort() | Modifies the list | The list after sorting |
| copy = t[:]; copy.sort() | Leaves t unchanged | A sorted defensive copy |
Use sorted() when you need a new sorted list, or copy the list before using sort() when the original must be preserved.
t = [3, 1, 2]
ordered = sorted(t)
print(t)
print(ordered)
t.sort()
print(t)- 1The list t is created with its original order.
- 2Calling
sorted(t)provides a separate sorted result stored in ordered. - 3Printing t immediately afterward shows the original order remains.
- 4Calling the sort method on t changes the order of t.
sorted(t)produces a new sorted list.- The first print shows that t was not modified by
sorted(t). - ordered contains the sorted result.
t.sort()changes t itself.
[3, 1, 2]
[1, 2, 3]
[1, 2, 3]Following Mutation Paths
Finding the Source of an Unexpected List Change
A program needs to keep an original list while changing another list. Determine why the original changes and select a safer assignment.
- 1Trace the assignment
If the program uses orig = t, both names refer to the same list object. This creates aliasing.
- 2Trace the mutation
When the list is changed through t, the shared list object changes. The changed contents are therefore visible through orig as well.
- 3Replace the shared reference
Use orig = t[:] when an independent list is needed. The slice creates a defensive copy.
- 4Check sorting choices
Use
sorted(t)for a new sorted list without modifying t. If using the sort method, first make a defensive copy when t must remain unchanged.
The unexpected change comes from aliasing. Use slicing for an independent list, and choose sorted() or a copied list with sort() according to whether the original must stay unchanged.
Common mistake Assuming two variable names automatically mean two independent lists.
What it looks likeorig = tWhy it failsAliasing means both names point to the same list object.
Do this insteadUse orig = t[:] when the second variable needs an independent list.
Common mistake Using the list sort method when the original order must be preserved.
What it looks liket.sort()Why it failsThe sort method modifies the list.
Do this insteadUse sorted(t), or create a defensive copy and call sort() on that copy.
Common mistake Expecting sorted(t) to reorder t itself.
What it looks likesorted(t)Why it failssorted()produces a new sorted list without modifying the original.Do this insteadStore the returned list, such as ordered = sorted(t).
Common mistake Using sorted as a variable name.
What it looks likesorted = [1, 2, 3]Why it failsThe variable shadows the built-in function name.
Do this insteadChoose another variable name so sorted() remains available.
Debugging Practice
A program must retain an original list and also produce a sorted version. Write a short plan that names the safest operation for each step: creating the independent version, sorting without changing the original, and sorting a copy with the list method.
Hints
- Use slicing when you need a defensive copy.
- Use
sorted()when you want a new sorted list without modifying the original. - Use the sort method on the copy if you first preserve the original with slicing.
- 1Aliasing is the central debugging hazard in this topic: two variable names can point to one shared list object, so a mutation through one name appears through the other. Follow references when tracing an unexpected change. Use orig = t[:] to make an independent list. Use
sorted()for a new sorted list that leaves the original unchanged, or copy first and then usesort(). Keep sorted available by never using it as a variable name.
Key Takeaways
- Aliasing occurs when two variables point to the same list object.
- A mutation through one alias affects what the other alias shows.
- Use orig = t[:] to create a defensive copy that can be modified independently.
- Use
sorted()for a new sorted list, and usesort()when you intend to modify a list. - Do not use sorted as a variable name because it shadows the built-in function.