The Python Mystery Code Solution: A Tale of Two Mutabilities

 

The Python Mystery Code Solution: A Tale of Two Mutabilities





In the first post of our series, "Python Variables Demystified: What's in a Name?", we ended with a challenge. We presented a snippet of code and asked you to predict its output, hinting that the behavior would be different for integers and lists.

This post is dedicated to unraveling that mystery. Let's take a close look at the code that had you thinking—or maybe even doubting—your Python knowledge.

The Code In Question

Here is the exact same code snippet from the end of Post 1:

a = 500
b = a
a = a + 100

list1 = [1, 2]
list2 = list1
list1.append(3)

print(f"b is {b}")
print(f"list2 is {list2}")

If your prediction matched the actual output below, give yourself a pat on the back! If not, don't worry—let's walk through it step-by-step.

The actual output is:

b is 500
list2 is [1, 2, 3]

Why is b still 500 but list2 magically became [1, 2, 3]? The answer lies in one word: Mutability.


The Walkthrough: A Line-by-Line Autopsy

Let's dissect the two halves of this code separately.

Part 1: The Integer Story (Immutable)

a = 500

We create an integer object 500 and bind the name a to it.

a  --> [500]
b = a

We bind the name b to the same object that a is currently bound to. We now have two names for one object.

a  --> [500] <-- b
a = a + 100

This is the critical line. Let's break it down:

  1. The expression a + 100 is evaluated. Since a points to 500, this calculates the new value 600.
  2. Because integers are immutable, the original 500 object cannot be changed. Therefore, the operation creates a brand new integer object 600.
  3. The assignment operator = then takes the name a and re-attaches it to this new 600 object.
  4. The name b is left entirely untouched, still bound to the original 500.

The final state of our names and objects is now:

a  --> [600]

b  --> [500]

This is why print(f"b is {b}") outputs b is 500.

Part 2: The List Story (Mutable)

list1 = [1, 2]

We create a list object [1, 2] and bind the name list1 to it.

list1  --> [1, 2]
list2 = list1

We bind the name list2 to the same object that list1 is bound to. Again, two names for one object.

list1  --> [1, 2] <-- list2
list1.append(3)

This is the other critical line. Let's break it down:

  1. We call the .append(3) method on the object that list1 points to.
  2. Because lists are mutable, this operation changes (or mutates) the existing list object in-place. It does not create a new list.
  3. The list object that both list1 and list2 are pointing to is updated.

The final state of our names and objects is now:

list1  --> [1, 2, 3] <-- list2

Both names still point to the same, now-modified, single list object. This is why print(f"list2 is {list2}") outputs list2 is [1, 2, 3].


The Moral of the Story

This mystery code perfectly illustrates the core principle from our first post: Variables are names, not boxes.

The different outcomes aren't a contradiction; they are a direct and logical consequence of this principle when applied to objects with different properties—mutable and immutable.

  • With immutable objects (like integers), operations that seem to "change" the value force the variable name to point to a new object.
  • With mutable objects (like lists), operations can change the contents of the original object, and every variable name pointing to that object will reflect the change.

Understanding this distinction is perhaps the most powerful step you can take toward mastering Python. It will help you debug confusing issues, design better APIs, and truly reason about what your code is doing.

Ready to see how this concept causes real-world bugs (like with function defaults)? Continue your journey with us in the next post: Mutability, Immutability, and Their Consequences.


Aaron Rose is a software engineer and technology writer at tech-reader.blog and the author of Think Like Genius.

Comments

Popular posts from this blog

The New ChatGPT Reason Feature: What It Is and Why You Should Use It

Raspberry Pi Connect vs. RealVNC: A Comprehensive Comparison

Running AI Models on Raspberry Pi 5 (8GB RAM): What Works and What Doesn't