Debugging Unexpected Changes in Data
A reference is the association between a variable name and an object. When you assign b = a, both variables refer to the same object—b is an alias for a.
The Unexpected Change
A common debugging surprise begins with a belief that assigning b = a creates a separate copy of whatever a refers to. For mutable objects such as lists, that belief is incorrect. The assignment creates another variable name associated with the same object. If the object is then changed through one name, the change can be observed through the other name.
What do you think happens?
Suppose a list is assigned to a, then b = a is executed. If b is used to change the list, will a still show the original list?
Reveal answer
Answer: No, because a and b refer to the same list.
The assignment b = a makes b an alias for a. Both names refer to one underlying list, so a mutation through b is visible through a.
Two Names, One List
A reference is the association between a variable name and an object. When b = a is assigned, Python associates both names with the same object. The second name is called an alias for the first name in this situation. The important point is that the assignment does not create a second list.
The is operator checks whether two variables refer to the same object. If a is b evaluates to True, the two names are aliases for that object.
Tracing a List Mutation
After the first assignment, a refers to a list containing 1, 2, and 3. The statement b = a gives the same list a second name. The append operation changes that one list in place. Consequently, both a and b now provide access to the list containing 1, 2, 3, and 4.
a: [1, 2, 3, 4]
b: [1, 2, 3, 4]Following the Bug
When an unexpected value appears, trace the data from the operation that changed it to every variable that can observe it. In an aliasing bug, the unexpected path is short: one name performs a mutation, the shared list changes, and another name later reads the changed list. There is no second list whose contents could remain independent.
Lists and Strings
| Object | What assignment does | What an apparent change does |
|---|---|---|
| List | A second variable can refer to the same list | A mutation changes the shared list, so aliases observe the change |
| String | A second variable can refer to the same string | An operation that appears to change the string creates a new string instead |
After b = a, both names can refer to the string banana. The expression b.upper() does not modify that string in place. It creates the string BANANA, and b is rebound to the new string. The variable a continues to refer to banana. This is why aliasing is not a practical problem for strings in the same way it is for lists.
Preventing Shared-List Bugs
If two lists must be independent, create an explicit copy instead of assigning the original list to a second name. The source material gives two list-copying strategies: slicing with new_list = old_list[:] and using the list constructor with new_list = list(old_list). After either strategy, changes to the new list are intended to be separate from changes to the original list.
Mistakes to Catch
Assuming b = a creates a copy of a list.
Both names refer to one underlying list, so the mutation is visible through a and b.
Fix:
Use a slice or the list constructor when an independent list is required.Treating a list mutation as if it were string reassignment.
Lists are mutable, whereas strings are immutable according to the source material.
Fix:
Ask whether the operation changes the existing object or creates a new object and rebinds a variable.Calling shared-list behavior a Python error.
The result follows from a and b referring to the same list.
Fix:
Trace the references and check whether the names are aliases.
A program needs two independent lists. One variable already refers to a list named old_list. Write the assignment that creates new_list as an explicit copy using slicing, and then write the equivalent assignment using the list constructor.
Hints
- The slicing form uses the complete slice of old_list.
- The constructor form receives old_list as its argument.
Debugging Checklist
- Find the variable whose value changed unexpectedly.
- Check whether another variable was assigned from it with a statement such as b = a.
- Determine whether the shared object is mutable, such as a list, or immutable, such as a string.
- For a list, look for a mutation performed through any alias.
- Use the is operator to check whether two names refer to the same object.
- Create an explicit list copy with slicing or the list constructor when independent data is required.
Key Takeaways
- A reference associates a variable name with an object.
- The assignment b = a makes b an alias for a; it does not create a second list.
- Mutating a shared list through one alias changes the one underlying object seen through every alias.
- Strings cannot be changed in place, so an apparent string modification creates a new string and rebinds the variable.
- Use slicing or the list constructor to create independent lists when shared mutable data would cause a bug.