My Neighbours Are Moving House • Mutating The Immutable Tuple (Sort Of)
Tuples are immutable, but their contents can change. Eh?! Let me tell you the story of my neighbours moving house, and all will make sense.
You may have already seen the "party trick" showing how to mutate an immutable tuple. And if you haven't, you will shortly. But I want to dig a bit deeper, not too much, but just enough to see what's underneath the surface.
And yes, my neighbours' imminent house move is relevant.
My House And My Neighbours
We live on Python Street. The houses have been around for well over a century. We're at number 3, and we have the Cleeses at number 1 and the Palins at number 5 as our next-door neighbours. Maggie, the neighbour at number 6 across the road, is learning to code in Python. She wrote code to keep track of all the street's residents. The code reads data from a spreadsheet into a Python data structure and then writes the data again into the spreadsheet. But this is not important. What matters for this story is what happens in the Python code.
Here are the first three entries of the data structure in the program:
Maggie reasoned that the houses in the street had been there for over a century and were not going anywhere. And there's no space for new houses. The sequence of houses in Python Street is immutable. Therefore, Maggie also chose an immutable sequence to represent the street in her Python code: a tuple. Then, she represented each house with a dictionary. Her version included more items for each house than the ones shown here. She wanted to record everything about the neighbours! But I'll spare you the details of Trevor's cat's name from number 11.
Mutable and Immutable
"Tuples are like lists but they're immutable" is a common description of tuples. I often use a similar phrase when introducing tuples to beginners. As this article focuses on mutability and immutability, it's best to briefly review what these terms mean for those needing a refresher.
A mutable object is an object whose value can change. For example, the list my_numbers = [3, 5, 8]
can change by removing, adding, or replacing a value. It remains the same object but with different values.
An immutable object is "an object with a fixed value" (quoted from the official documentation). Once you create an immutable object, its contents cannot change for the duration of the program:
Since the tuple is immutable, you can't replace the first value in other_numbers
. Strings are another common immutable data type.
One last aside before we get back to Python Street and Maggie's code. Often, students tell me, "Aha! You're wrong. See how strings are mutable", and then show me this:
No, you haven't found a bug in Python. I'll return to this point later, as it's one of the threads I want to follow in this article. But here's a brief answer for the impatient ones among you.
You start with the original string object, which you name publication
. The method call publication.upper()
returns a new string object with all characters uppercase. This is not the original object. But you assign this new object to the old name, publication
. The variable name no longer refers to the original object but to the new one now. Therefore, the object hasn't changed. Instead, you created a new object and used the old name. You reused an old label on a new box!
The Cleeses Move Out
The Cleeses are moving out. We liked the Cleeses as neighbours. The dad has this funny walk he'll do to make our kids laugh. The new residents aren't moving in for another month. So Maggie, who can't leave her records in an out-of-date state for even a minute, went ahead with the change:
I'm using the pretty print module pprint
to display the output:
({'House number': 1, 'Number of residents': 0, 'Residents': None},
{'House number': 3, 'Number of residents': 4, 'Residents': 'The Gruppettas'},
{'House number': 5, 'Number of residents': 2, 'Residents': 'The Palins'})
The first item in the tuple street
now shows there are no residents in number 1, Python Street.
But hang on, isn't street
a tuple? And aren't tuples meant to be immutable? In the words of the official Python documentation, they're "an object with a fixed value". But the contents within the data structure have changed. The Cleeses are no longer there.
Could the same thing be happening as in the example of the string publication
earlier? No. Let's confirm this:
The output shows the object id is the same before and after the change:
Before the Cleeses moved out
({'House number': 1, 'Number of residents': 3, 'Residents': 'The Cleeses'},
{'House number': 3, 'Number of residents': 4, 'Residents': 'The Gruppettas'},
{'House number': 5, 'Number of residents': 2, 'Residents': 'The Palins'})
4420647232
After the Cleeses moved out
({'House number': 1, 'Number of residents': 0, 'Residents': None},
{'House number': 3, 'Number of residents': 4, 'Residents': 'The Gruppettas'},
{'House number': 5, 'Number of residents': 2, 'Residents': 'The Palins'})
4420647232
This confirms the object has not changed. It's the same tuple, which is meant to be immutable. But its contents are most definitely different.
What Does The Tuple "Contain"?
Let's try something else before I answer the question in the subheading.
What's the size of the tuple street
?
The function sys.getsizeof()
returns the object's size in bytes. Here's the output:
64
"So what?" you might be thinking. Let's look at the size of one of the dictionaries within street
:
And this outputs:
184
One of the dictionaries within the tuple is 184 bytes, but the tuple is only 64 bytes. And recall that there are three dictionaries in the tuple.
We can go further. The size of street
, which contains three dictionaries each containing several items, is the same as a simple tuple containing one of the most lightweight objects you can find:
This is because a tuple does not "contain" the dictionaries within it, technically speaking. Instead, it contains references to those dictionaries. This is the case for other data structures, too.
Tuples Are (Still) Immutable
With this knowledge, let's see what's happening to the tuple when the Cleeses move out of Python Street. I'll show the object ids for the three dictionaries before and after the change:
I'm using a rather compact way of printing out the three ids for the dictionaries using the unpacking operator *
, a generator expression, and the sep
parameter in print()
. As it's not the main point of this article, I won't expand on this, but feel free to discuss it either in the comments here or on The Python Coding Place forum (for members of The Place!)
The output is the following:
Before the Cleeses moved out
({'House number': 1, 'Number of residents': 3, 'Residents': 'The Cleeses'},
{'House number': 3, 'Number of residents': 4, 'Residents': 'The Gruppettas'},
{'House number': 5, 'Number of residents': 2, 'Residents': 'The Palins'})
Ids for the three dictionaries
4375184640
4375019456
4375185920
After the Cleeses moved out
({'House number': 1, 'Number of residents': 0, 'Residents': None},
{'House number': 3, 'Number of residents': 4, 'Residents': 'The Gruppettas'},
{'House number': 5, 'Number of residents': 2, 'Residents': 'The Palins'})
Ids for the three dictionaries
4375184640
4375019456
4375185920
Compare the id numbers of the three dictionaries before and after. They're the same. This means the dictionary objects are the same, including the first one, which has different contents. Dictionaries are mutable, and therefore, their values can change. But since they're the same objects before and after the change, they have the same id. The tuple street
contains references to these objects, and these references haven't changed.
Therefore, the tuple's values haven't changed. They're exactly the same before and after the Cleeses moved out. The tuple street
contains the same references to the same dictionary objects. One of those dictionaries changed, but not its reference in the tuple.
This line of reasoning may seem like a child would use to argue a "technicality". But this is no childish topic!
Tuples are immutable. We knew that all along, right?!
And Now For Something (Not) Completely Different
A post on social media by my friend
(of The Python Papers) earlier this week inspired me to add a bit more to the draft of this article. Mike's quiz looked something like this:The output is:
[1, 2, 3]
Sorry, I should have put a spoiler alert there!
I'll discuss this soon. But, even more intriguing are the following two variations. I'm showing the output as a comment below each block:
Note that in all three examples, the variable numbers
contains all six numbers. It's the variable integers
that changes as we move from the option using addition +
and assignment =
, to the option using the in-place addition operator +=
, and finally to the option using the in-place addition operator with tuples instead of lists.
Digging Underneath The Surface (A Bit)
Let's look at all three examples.
1. Using lists and the addition +
and assignment operators =
In the first example, the one in Mike's quiz, you start with a list numbers
containing three values. The second line, integers = numbers
, does not create a copy of the list. Instead, you now have two variable names, numbers
and integers
, both referring to the same object. These are not two objects with equal values. They're the same object. You can use the built-in id()
to verify this.
The third line, numbers = numbers + [4, 5, 6]
, performs two operations. First, you add the list numbers
to another list, [4, 5, 6]
. This creates a new list, a third one, containing all six values. You assign this new list to the name numbers
. Therefore, the variable name numbers
is no longer associated with the original list you created on the first line. It's now pointing to the new list created from the addition. But integers
still points to the original list, which only contains the first three values.
2. Using lists and the in-place addition operator +=
The in-place addition operator is not just "syntactic sugar" for the separate use of +
and =
. This example proves this point. The statement numbers += [4, 5, 6]
modifies the original list, extending it to include three additional numbers. Since this no longer creates a new object but modifies the existing one, numbers
still refers to the same object it did when you first assigned it. And integers
refers to that same object, which has now mutated to include numbers up to six.
3. Using tuples and the in-place addition operator +=
But the behaviour is different when you use the in-place addition operator +=
with tuples. Tuples are immutable. Therefore, the same object cannot be modified. Instead, the in-place addition operator creates a new tuple object, which is then assigned to the old variable name numbers
. This doesn't affect integers
.
Final Words
Understanding mutability and immutability helps us understand how and why similar operations behave differently when applied to different data types.
This is why, if you have a list numbers = [1, 2, 3]
, all you need to do is call a method to modify it, for example numbers.append(4)
. Since lists are mutable, the original list that's assigned to numbers
changes. But the behaviour is different when using strings. You've seen this example earlier with the string publication = "The Python Coding Stack"
. If you call publication.upper()
, you don't modify the original string. Instead, the method returns a new string. You can then assign this new string to the old variable name if you wish. But that's not the same as changing the string object itself, which you can't do.
Back on Python Street, the new neighbours have arrived. They're the van Rossums…
A note on my "neighbours". John Cleese (the one with the funny walks) and Michael Palin were two of the Pythons in the Monty Python comedy group. Stephen Gruppetta, alas, wasn't! And the creator of the Python programming language, Guido van Rossum, named the programming language after the comedy group. So it's all linked, you see…
Code in this article uses Python 3.12
Stop Stack
#48
The Python Coding Place is live. There are several courses on there already (some free, many for members), and I have a schedule to add several more courses almost on a weekly basis for the next two months. There's a time-limited 50% off for those who want to join The Python Coding Place in this introductory stage: thepythoncodingplace.com. The Place is the hub for all my resources: video courses, members' forum, live cohort courses, weekly videos, and more.
There's already been some interesting discussions with members on the forum. The Python Coding Place already has 82 members from the pre-launch phase. Let's get to 100…
If you read my articles often, and perhaps my posts on social media, too, you've heard me talk about The Python Coding Place several times. But you haven't heard me talk a lot about is Codetoday Unlimited, a platform for teenagers to learn to code in Python. The beginner levels are free so everyone can start their Python journey. If you have teenage daughters or sons, or a bit younger, too, or nephews and nieces, or neighbours' children, or any teenager you know, really, send them to Codetoday Unlimited so they can start learning Python or take their Python to the next level if they've already covered some of the basics.
Recently published articles on The Python Coding Stack:
Put On Your Deerstalker. You're Now a Detective. It's Time For Debugging Debugging Python Code Is Like Detective Work
Why Can't I Just Use A List? • Understanding NumPy's
ndarray
(A NumPy for Numpties article) From Python's native lists to NumPy'sndarray
data type, with a glimpse at the built-inarray
. Why do we need all these similar data structures?next(years) An end-of-year post • Some reflections • And there's some Python stuff in this post, too—a spinning globe animation
Do Not Try This At Home A bit of silliness for the holiday season • But please, don't code like this. Please • Plus some out-of-the-norm commentary • There's nothing ordinary about today's article
The Key To The 'key' Parameter in Python A parameter named
key
is present in several Python functions, such assorted()
. Let's explore what it is and how to use it.
Recently published articles on Breaking the Rules, my other substack about narrative technical writing:
The South Park Technical Writing Manual (Ep. 14) What can we learn from South Park? Yes, the satirical TV show
I Haven't Been Abducted by Aliens (Ep. --) Why this long lull since the last Breaking The Rules post?
The Selfish Reason (Ep. 13) Another reason for authors to innovate • Enjoying the writing process
The Consequential Detail (Ep. 12). Can a single letter or one blank line make a difference? (Spoiler Alert: Yes)
The Unexpected Audience (Ep. 11). What I'm learning from listening to Feynman's physics lectures
Stats on the Stack
Age: 9 months, 1 week, and 2 days old
Number of articles: 48
Total subscribers: 1,814
On the Paid tier: 91
Each article is the result of years of experience and many hours of work. Hope you enjoy each one and find them useful. If you're in a position to do so, you can support this Substack further with a paid subscription. In addition to supporting this work, you'll get access to the full archive of articles and some paid-only articles. Alternatively, if you become a member of The Python Coding Place, you'll get access to all articles on The Stack as part of that membership.
This was great - I definitely predicted the title wrong haha. I also recently learned that simultaneous assignment is not perfectly simultaneous!! Keep it up Stephen
I learned a lot with this one. When you started with talking about being able to change the values in a dictionary in a tuple, my first guess for why was *close*. I figured that the values of the tuples were the dictionary, so as long as the dictionary is still there, the tuple hasn't changed, no matter if changes are made inside the dictionary. But it makes sense that it's not the actual object in the tuple but a reference to it.
But I have a question about reassigning the variable name of a string (or any other immutable type). When you assign the new string to the old string's variable name, what happens to the old string? Is it gone, removed from memory or just hanging out, unreachable until garbage collection?