Concepts / Understanding Mutable and Immutable Objects

Understanding Mutable and Immutable Objects

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
Interactive lab

Try it: Names and Objects

How Python variables are names bound to objects: assignment never copies, mutating a list is seen through every name that points at it, and ints are replaced rather than changed.

How it works

  1. name = object binds a name; b = a makes b point at the same object.
  2. Mutating a list (append, +=) changes the one shared object.
  3. a = a + [x] and y = y + 1 create NEW objects and re-point one name.
  4. Passing a list to a function passes the reference, so the function can change it.

Default run (7 steps): Two names, one list. Every name is a label that points at an object. … Printed: [1, 2, 3] / True

Simplified: Six fixed scripts on a tiny heap; you choose the values. Object numbers are illustrative, not real id() values.

Educational simulation

Loading the simulation…

One Object, Two Names

A useful way to understand Python assignment is to separate variable names from objects. A reference is the association between a variable name and an object. When you assign b = a, Python does not create a second object as part of that assignment. Instead, both names refer to the same object. The second name, b, is an alias for a.

refers torefers toa[1, 2, 3]one list objectb
What object do a and b refer to after b = a, and how are the variable names connected?

The important question after b = a is not whether the values look equal. The important question is whether both names refer to one object. The is operator checks whether two variables refer to the same object. If a is b returns True, a and b are aliases.

Mutation Travels Through Aliases

What do you think happens?

Suppose a and b refer to the same list, and the first item is changed through b. What will a show afterward?

  • The original list, unchanged
  • The changed list
  • A separate copy of the list
Reveal answer

Answer: The changed list

Lists are mutable. There is one underlying list object, so changing that object through b makes the change visible through a as well.

Following a List Alias

Let a refer to the list [10, 20]. Then let b = a. Change the first item through b. What do a and b represent afterward?

Create the list: The name a refers to one list object containing 10 and 20.

Create the alias: The assignment b = a makes b refer to the same list object. It does not create an independent list.

Mutate through b: Changing the first item through b changes the one shared list object.

Inspect both names: Both a and b now show the list [99, 20], because both names still refer to that same changed object.

Both aliases refer to one list whose first item is now 99.

refers torefers toshowsshowsa[10, 20][10, 20]one shared lista[99, 20][99, 20]one shared listb[10, 20]b[99, 20]
What changes when a shared list is mutated through b, and what do both aliases show afterward?

This behavior is not a Python error. It follows directly from there being only one underlying list object. A modification made through one alias is a modification of that object, so every other alias observes the updated contents.

Lists and Strings Behave Differently

The practical danger of aliasing depends on whether the object is mutable. Lists are mutable: their contents can be changed. Strings are immutable: the string object cannot be changed in place. Therefore, aliases to a list can expose unexpected changes, while aliases to a string do not create the same problem.

refers torefers tostill refers torebound toa[1, 2][1, 2]can change in placeabananabananacannot change in placeb[1, 2]bBANANABANANAnew string object
Why does changing a list through one alias affect the other name, while changing a string creates a different object instead?

An Alias to a String

Let a and b refer to the same string, banana. Then apply an uppercase operation to b and assign the result back to b. What happens to a?

Start with one string: Both a and b can refer to the string banana.

Apply the operation: The uppercase operation does not modify banana in place. It creates the string BANANA.

Rebind b: Assigning the result back to b makes b refer to BANANA.

Inspect a: The name a still refers to banana. The original string was not changed.

a refers to banana, while b refers to the new string BANANA.

Reassignment Breaks One Connection

Mutation and reassignment are different operations. Mutation changes the contents of an existing mutable object. Reassignment changes which object a variable name refers to. If a and b both refer to one list and b is reassigned to another object, a continues to refer to the original list. Only b's connection has changed.

still refers tonow refers toa[1, 2]original listb[8, 9]new object
What changes when b is reassigned to a new object, and which variable still points to the original list?

Finding Aliasing Bugs

An aliasing bug occurs when a program unintentionally gives two names access to the same mutable list. A later operation through one name then changes what the other name sees. The bug is often not in the list operation itself; the problem is that the program created a shared reference when it needed independent lists.

refers torefers toshowsshowsfirst["red"]["red"]one shared listfirst["red", "blue"]["red", "blue"]one shared listsecond["red"]second["red", "blue"]
Where does a second variable unexpectedly share a list with the first, allowing an operation on one to change the other?
  • Assuming b = a creates a separate list

    Assignment makes both names refer to the same list object.

    Fix: Decide whether shared access is intentional. If not, create an explicit copy.

  • Treating a list mutation like string replacement

    Lists are mutable, whereas strings are immutable.

    Fix: Classify the object before predicting the effect of an operation.

  • Assuming reassignment changes every alias

    Reassignment changes one variable's reference; it does not automatically change other variable names.

    Fix: Track each variable name and the object it currently refers to.

  • Avoiding all assignment instead of controlling shared references

    The source material advises intentional use of aliases rather than avoiding assignment entirely.

    Fix: Use aliases when shared mutation is intended and copies when independent lists are required.

Copying Lists Intentionally

When two lists should be independent, create an explicit copy instead of assigning the original list to another name. The source material gives two strategies: slicing, as in new_list = old_list[:], and the list constructor, as in new_list = list(old_list). These approaches create an independent list for the situation described here, so changing one list does not intentionally share the same list object with the other name.

refers torefers toold_list[1, 2][1, 2]original listnew_list[1, 2][1, 2]copied list
How does assigning a copy change the connections between variables and lists, and which mutations remain independent?

Choose deliberately between sharing and copying. Shared references are appropriate when multiple names should access one mutable list. Use slicing or the list constructor when the names should have independent lists and later mutations should not be shared.

EASY

A variable a refers to a list. You want b to begin with the same contents, but you want later list changes through b to remain independent from a. Which strategy should you use, and why?

Hints
  • Ask whether b = a creates a new list or another reference.
  • Use one of the two explicit list-copying strategies described in this section.
  • Your explanation should mention the difference between one shared object and two independent list objects.

A Reliable Tracing Method

  1. List the variable names involved.
  2. Record which object each name refers to after every assignment.
  3. Check whether an assignment creates an alias, creates a copy, or rebinds a name.
  4. Classify the object as mutable or immutable.
  5. If the object is mutable, ask whether the operation changes that object in place.
  6. Predict what every name referring to that object will show afterward.
  7. Use the is operator when you need to check whether two names refer to the same object.

Tracing a Mixed Scenario

A list is assigned to a, b is made an alias of a, and c is made from an explicit copy of a. Which names are affected when the shared list is mutated?

Track a and b: The assignment b = a gives both names access to the same list object.

Track c: Creating c with a list-copying strategy gives c an independent list with the same starting contents.

Mutate through b: The mutation changes the list shared by a and b.

Compare the results: a and b show the changed contents. c retains its own list contents because it does not refer to the shared list.

The mutation propagates to aliases of the original list, but not to the independently copied list.

Key Takeaways

  1. A reference connects a variable name with an object.
  2. After b = a, both names refer to the same object; b is an alias for a.
  3. Mutating a shared list through one alias changes what every alias sees.
  4. Strings are immutable, so an operation that appears to change a string creates a new string instead.
  5. Use slicing or the list constructor when independent lists are needed.

Key Takeaways

  • Aliasing means that multiple variable names refer to one object.
  • Lists are mutable, so a mutation through one alias is visible through all aliases.
  • Strings are immutable, so string operations create new strings rather than changing the original string in place.
  • Reassignment changes one variable's reference, while mutation changes a mutable object's contents.
  • Use slicing or list() to create independent lists when shared mutation is not intended.