Python Im/Mutable Ninja Objects
or Everything about mutable and immutable objects in Python
In Python, everything is an object, as you may have already heard and will continue to hear for as long as we shall exist. And objects can be either mutable or immutable — which has nothing to do with ninja turtles as the title of this post alludes to, and everything to do with how they relate to each other, how to edit, how they are passed to functions and much more. Sorry about the title, it was just an eye-catcher. This post will be all about Python objects, and not at all about a supercool 80’s cartoon. The objective is to clarify the definitions and behaviors and make this collection of concepts a bit more friendly to comprehend. And I promise, it is kinda supercool too…
To begin, at the very start of it all, I will explain some basic definitions, just so that the basic knowlegde is set before continuing any further:
OOP…s I did it again
I tricked you into thinking there will be popular culture content, but no, in this context OOP is not the first three letters of the title of a 2000’s anthem, but an abbreviation for Object Oriented Programming, which is the programming model under Python falls and which organizes software design around objects rather than functions and logic. Everything is an object, remember?
Objects
What are they really?
I like to define objects in Python as boxes. A new object is a new box that has content. Let’s say an int
. I define my int
as a = 5
. This automatically creates a new object (box) with the content 5
. The content of an object is called the value
. This box has a label, and the label is a
. These labels are called object references in Python, used to acces the object’s value or to manipulate the object references themselves. So far, so good.
But — what happens if I then write b = 5
? In Python, when the content is the same and a different variable is assigned to it, in memory, the new variable is created as a new label to the already created object. Therefore, my box (object) containing the value 5
, will have two labels stuck to it: a
and b
. Only when both labels are deleted, the object is deleted. This is donde to optimize the use in memory and not have several objects with the same values created along the memory.
Any object in Python is defined by three main things: its id
, its type
and its value
. This constitutes a very brief intorduction to objects, but it is enough to get on with what comes next.
What is an id?
Just like an ID card usually has a unique number that belongs to a person, the id in python is a unique piece of information that belongs to one particular object. This information represents the address in memory where that specific object is located. Not two objects will be located in the same address in memory, therefore every id will be unique to every object. Also, not every object will be created every time in the same address in memory. But, for as long as an object exists, the id will be unique to them and them only.
Kind of like the lockers outside a supermarket or a store. When you come in with a backpack or bags from other stores, you store your belongings in a locker with a number, and you take the key. You choose the locker randomly out of the ones available — not already in use by other customers. Your belongings remain there for as long as you are shopping. The locker number is your id, your belongings is the object, and your time in the supermarket will be the time the object exists in your program. Once you leave, the locker you were using (address in memory) will be released and ready for the next customer.
To find the id of an object, you simply use the function id()
:
It will automatically print the memory address of the list a
that I created right before.
Adding to the explanation of the objects above, if we define a the examples of the variables a
and b
with value 5
, the id
printed will be the same:
So far, so good.
Let’s continue…
What is type?
The type
of an object, just like in many other languages, describes the type of element an object is. For example, some typical built-in types in Python are: int
, string
, float
, list
, set
, and many more.
As the id
defines the address in memory, the type
specifies its behavior — which includes the values it might represent or the actions that can be performed to it.
For example, if an object is a dictionary
, keys and values can be assigned to it. But you cannot do that to a string
, yet strings can be added one to another and dictionaries cannot.
In the image above, we can see the difference in behavior of two different types when a same action is carried out on them.
As well as the id
, the type
of an object will never change.
To know the type
of an object, we can use the function type()
. Just like with the id
, we pass the object to the function and it will return the type:
As you can see, the return is <class '..'>
instead of only the type
. This is because like we’ve said before, and will continue to do so, everything is an object and therefore everything belongs to a class
. But, that is a subject for another post.
Mutability
Let’s try the id
thing again, but this time with a list. It should work the same, shouldn’t it?
Whoa, whoa, whoa… what! The id
's are not the same, but the values are. Why!?
Okay, this opens the opportunity to introduce the next subject that brings us to this post which is: mutability.
Mutability, in Python, refers to the ability of edition of an object: an object can be mutable or immutable. After creation, if the value of an object can change, the object is called mutable, while if the value cannot change, the object is called immutable.
- Which data types are mutable?
Some of Python’s mutable data types are: lists, byte arrays, sets, and dictionaries. - Which data types are immutable?
Some immutable types include numeric data types, strings, bytes, frozen sets (a special version of sets), and tuples.
Let’s see this in an example:
We will create an int
a
, print its id
, then “change” its value and print the id
again:
As we can see, the id
value changed, which means that instead of updating the value of the object, a new one is created with a new value, in another place in memory. This represents the behavior in memory of mutable objects.
Next, we will do the same, but with a list
:
In this case, the id
did not change, which means that the object changed its value, but remained in the same place in memory. This is the behavior in memory of immutable objects.
Now let us continue with another behavior in Python regarding references and assignments:
Like I said before, objects are boxes in memory that have content (values) and have a label to refer to them. To set a label to an object, like we’ve also seen before, we do the simple task of, for example: a = 5
. The object with value 5
, has the label a
. This is is referencing.
But, let’s check out the following example:
Here, we referenced a
to 5
. Then, we assigned b
to a
. We printed both, and the output is the same. Then, we referenced a
to 10
, and printed both values again. Yet, they are not the same.
But how come? Didn’t we tell b
to be equal a
?
Yes and no. In Python, when we reference, we are assigning a reference to an object. That is what we did with a = 5
. But, when we did b = a
, we were not telling b
to be equal to a
. There, we were assigning b
to whatever object a
is pointing to — continuing with the box and labels example, we were sticking the label b
to the same object where a
is. That is why when printed, both a
and b
printed the same value — because they were both referring to the same object. Then, when we referenced a
again, because the number types are immutable, a new object is created with the new value 10
. So now, the label a
is removed from the object with value 5
, and stuck to the object with value 10
. The first object still has the label b
stuck to it, so when printing for a second time, the values are different — they each print the value of the object they are referring to.
Are you still here? Would you have rather read a blog post about Teenage Mutant Ninja Turtles? Nah, if you made it this far then you’re invested, so let’s continue, promise there’s not much left…
So far we’ve covered some of the main concepts regarding objects and mutability. There is a special case in mutability, which is tuples.
Ambiguity
By definition, tuples are mutable but their values can change. I know, this goes against everything we just covered above, but there is a logical explanation around this crazy statement. Tuples are a data type that stores collections of data, and are immutable. One characteristic of tuples is that as well as lists, they can contain different types of objects inside. This means that whatever a tuple contains could be either mutable or immutable. So, if a list has for example a list inside, the tuple itself cannot be modified, yet the list inside the tuple can, because it is mutable. Therefore: a mutable object’s value can be updated even when contained within an immutable container such as a tuple.
This is better understood through memory usage:
Remember how in immutable objects, when the value reassigned for the reference, a new object was created which means that its address in memory changes, but when reassigning a mutable object, the value was updated for that reference and the address in memory remained the same.
So, a tuple is immutable, and contains mutable or immutable data types, yet for every item inside the tuple, the address in memory does not change.
This means that the mutable objects can be edited, but the addresses the tuple references will never change, which makes this ambiguity possible.
Furthermore, when using mutable or immutable objects in functions, part of the behavior explained above affects the way one or another acts.
What better way to explain than with an example:
We create a function increment
that receives n
and increases n
by 1
.
Then we reference a
to 5
and exectute the function with a
. In theory, a
should now be 6, but when we print it, we can see that the value has not changed.
On the other hand:
In this example we create the same function increment
, but in this case we make it append
a value to n
. We create a list
and execute the function passing our object list
. When we print it afterwards, we can see that the value of increment
was actually appended to the list
.
Again, this is because of mutability. In the first case, a
is a number, an int
which is immutable as we learned. An in this case, its value was not affected by the function. The second case, a list
was passed to the funcition, and if you recall, the lists are mutable. When printed, the value changed.
Therefore we can conclude that immutable objects are only affected in the function’s environment and the new reference made will not exist beyond the limits of the function, while mutable objects are affected both in the context of the function and beyond it.
To finish, another curious built-in characteristic of Python referring to the first example we used in this blog:
Here, like before, we are executing a = 5
, b = 5
, and printing both of their is
's. They’re the same. Makes sense.
Then we’re executing a = 257
, b = 257
, and printing their id
's, yet their NOT the same…
WHYYYYYYYYYYYYYYYY!!
Let us not become desperate… belive it or not, this actually does make sense.
When we stated at the beginning that ina = 5
and b = 5
both a
and b
print the same value of id because they are pointing to the same object is absolutely true. But this only happens for a certain number of integers.
These are defined in the macros NSMALLPOSINTS
andNSMALLNEGINTS
and range from -5 to 256 (inc). These integers are considered to be the range most used in Python and are therefore predefined in objects for better use of the processes. The process of creating a new integer object and then destroying it immediately creates a lot of useless calculation cycles, so Python preallocated a range of commonly used integers. So, when we create an int with any of the values in this range, we’re actually creating a reference to these already predefined object.
When we create an in outside of this range, a new object is created each time, no matter the value.
And that concludes the first installment of Python and the wonderful secrets it keeps, its intricate yet simple mechanics and the importance of having it all very clear so as to make better use of resources and possibilities when coding!
:)
P.S.: Hopefully my next post will be about the Teenage Mutant Ninja Turtles, you deserve it.