Concepts / Debugging Unexpected Changes in Data

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.

  • Programming

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?

  • Yes, because b is a copy
  • No, because a and b refer to the same list
  • Only if the list contains numbers
  • Only if a is printed before b
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.

refers torefers toavariable name[1, 2, 3]one list objectbalias
What object do a and b refer to after executing b = a?

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

python

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.

same list after mutationsame list after mutationa[1, 2, 3]a[1, 2, 3, 4]b[1, 2, 3]b[1, 2, 3, 4]
What changes in the shared list when b.append(4) is performed?
Output
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.

changesis read throughb[0] = 9mutationshared listone underlying objecta[0]9
How does a mutation through one variable name affect the value observed through another name?

Lists and Strings

ObjectWhat assignment doesWhat an apparent change does
ListA second variable can refer to the same listA mutation changes the shared list, so aliases observe the change
StringA second variable can refer to the same stringAn operation that appears to change the string creates a new string instead
refers torefers tostill refers to bananaafter b.upper()alist[1, 2, 3]can change in placeabananaBANANAnew stringbsame listbbanana
What is the difference between changing a list through an alias and rebinding a string variable?
python

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.

refers torefers toold_list[1, 2, 3]original listone list objectnew_list[1, 2, 3]copied listanother list object
Which variables share the same object, and where should a copy be made to prevent an unintended mutation?

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.

EASY

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

  1. Find the variable whose value changed unexpectedly.
  2. Check whether another variable was assigned from it with a statement such as b = a.
  3. Determine whether the shared object is mutable, such as a list, or immutable, such as a string.
  4. For a list, look for a mutation performed through any alias.
  5. Use the is operator to check whether two names refer to the same object.
  6. 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.