Everything is an object in Python, and (im)mutable.

Checha Giudice
12 min readJan 16, 2021

--

Said some dude named Guido, I believe.

Life was dull, boring and predictable until one day, a long long time ago (in the late 80’s no less), a Dutch guy named Guido van Rossum was bored around Christmas time and decided, just for fun, to create a new scripting languaje that would get the attention of Unix/C hackers… you know, what your regular late-thirties guy does in the holidays.

It wasn’t that regular after all.

He released Python (name given because Van Rossum was a major Monty Python’s fan) in 1991, and evolved to what it is today:

An interpreted, high-level and general-purpose programming language.

What about that “everything is an object” quote in the title?

Just as simple as that… and now you are clueless.

It means every entity has some metadata and associated functionality. These attributes and methods are accessed via the dot syntax.

I lost you again, let’s start over:

Object-oriented programming (OOP) is a classification (a programming paradigm) based on the concept of “objects” which can contain data and code; data in the form of fields (attributes or properties), and code, in the form of procedures (methods).

For you to understand better: nothing beats a real-life example.

You, for sure, make grocery lists. You list every item in a piece of paper. If you are a little bit obsessive-compulsive (it can happen if you are a programmer), you write them in a specific order.

I can imagine as well, you don’t use your dictionary anymore (because Google, right?), but you know how to use it, you know there’s a definition for every word you are looking for.

Python has lists and dictionaries, and they work almost the same as your real-life lists and dictionaries. The lists let you add (store) multiple items in one single variable (grocery list) in a specific order. Dictionaries, on the other hand, are used to store data values in key:value (name:definition) pairs.

Both, lists and dictionaries, are objects to Python, and first-class too. Creator Guido van Rossum designed the language according to the principle “first-class everything”.

“One of my goals for Python was to make it so that all objects were “first class.” By this, I meant that I wanted all objects that could be named in the language (e.g., integers, strings, functions, classes, modules, methods, and so on) to have equal status. That is, they can be assigned to variables, placed in lists, stored in dictionaries, passed as arguments, and so forth.” (Blog, The History of Python, February 27, 2009)

The “everything is object” quote also includes the functions of your program, which are blocks of statements which manipulate data: this is called the procedure-oriented way of programming.

Another way to organize your life better, would be combining data and functionality, and then wrap it into an “object”. This way is called object-oriented programming.

You can say, the better way is the old way (procedure-oriented way), but when writing more complex and long programs the other way (object-oriented way) is better suited to help with this problem.

How do I start working on object-oriented programming (OOP), then?

Glad you asked.

As I stated, (and now repeating myself in a different way) OOP is a method of structuring a program by bundling related properties and behaviours into individual objects.

You need to know about classes, too…

Classes and objects are the two main aspects of OOP. A class creates a new type where objects are instances of the class. As if you had variables of some type, that store that type of data, and are also instances (objects) of the type class.

Object creation in Python is a two-step process. In the first step, Python creates the object, and in the second step, it initializes the object. Most of the time, we are only interested in the second step (i.e., the initialization step).

When instantiating an immutable object, Python first checks if an object of the same value already exists in memory before instantiating a new object.

When instantiating a mutable object, Python does so immediately — all mutable objects must have their own unique identity.

I’m getting off topic, I apologise.

Actually, this blog was going to be about Mutable/Immutable objects, and I got carried away by Python magic dust (just kidding, I always beat around the bush).

Back in topic.

Since everything is an object int Python, as I said a hundred times in this same article, every variable holds an object instance. When an object is initiated, a unique id is assigned to it. At run time its type is defined, and once set it can never change. Not to say it’s state can not be changed, if it’s a mutable object.

Mutable: that can change after its creation.

Immutable: can’t change.

Given this, we can say now every object has:

  • Identity (id): the built-in function id() returns the identity of an object as an integer and most times corresponds to the object’s location in memory.
>>> CODE
x = 10
name = "John"
a_list = ["apple", "banana", "orange"]
print(id(x))
print(id(name))
print(id(a_list))
>>> OUTPUT
9079296
47344696268592
47344695554376
  • Type: the built-in function type() returns the type of an object (int, float, list, dictionary, etc).
>>> CODE
x = 10
name = "John"
a_list = ["apple", "banana", "orange"]
print(type(x))
print(type(name))
print(type(a_list))
>>> OUTPUT
<class 'int'>
<class 'str'>
<class 'list'>
  • Value.

What and which are mutable and immutable objects?

Mutable objects can change its state or contents. Those are:

  • Lists.
  • Dictionaries.
  • Set.
  • Byte array.

Immtuable objects can not change its state or contents. Those are:

  • int.
  • float.
  • complex.
  • string.
  • tuple.
  • frozen set (immutable version of set).
  • bytes.

Mutable vs. Immutable.

One way to understand object mutability is to find out if two variables point to the same object.

>>> CODE
a = 89
b = 100
print(a is b)
>>> OUTPUT
False

In this case, we have two int variables (same type) with different value. The is operator confirms whether a and b are pointing to the same object.

>>> CODE
a = 89
b = 89
print(a is b)
>>> OUTPUT
True

This example gives us a and b with same type and value (only different names). They are pointing to the same object. How is this possible?

To inhance Python’s memory performance; ints of the same value, but different name, are always pointing to the same object. Here’s the code to confirm it:

>>> CODE
a = 89
b = 89
print(a is b)
print(id(a))
print(id(b))
>>> OUTPUT
True
47360510393360
47360510393360

As I said before, same id means same memory address, therefore they are pointing to the same object. Just like Stefani Joanne Angelina Germanotta and Lady Gaga are the same person, just different names to address her.

And what if we say variable a has a value, and variable b is equal to variable a?:

>>> CODE
a = 89
b = a
print(a is b)
>>> OUTPUT
True

Python optimizes resources by making two names that refer to the same string value refer to the same object. Just like I said earlier: Stephanie getting Lady Gaga as her stage name.

Let’s shake things a little bit. What about changing ‘b’s value?

>>> CODE
a = 89
b = a + 1
print(a)
print(b)
print(a is b)
>>> OUTPUT
89
90
False

In our firsts examples, we saw that ‘a’ and ‘b’ had the same value, now that that has change adding one small integer, a and b are not pointing to the same object anymore.

That’s why ints are immutable objects.

Let’s test strings now.

>>> CODE
s1 = "This is a string"
s2 = s1
print(s1 == s2)
>>> OUTPUT
True

As we are using the ‘==’ operator, we are testing if both strings have the same value, but: are them pointing to the same object? Let’s try the same approach as we did with the ints:

>>> CODE
s1 = "This is a string"
s2 = s1
print(s1 is s2)
s3 = "another string"
s4 = "another string"
print(s3 is s4)
>>> OUTPUT
True
True

Indeed, just as ints, strings are immutable too.

Let’s test now a mutable object, a list, with the same tests:

>>> CODE
l1 = [1, 2, 3]
l2 = [1, 2, 3]
print(l1 == l2)
print(l1 is l2)
>>> OUTPUT
True
False

Immutable objects are something else, right? We are giving the EXACT same value to two variables of different name, and they are actually pointing to two separate objects. But what if we assign ‘l1’ to ‘l2’?

>>> CODE
l1 = [1, 2, 3]
l2 = l1
print(l1 == l2)
print(l1 is l2)
>>> OUTPUT
True
True

Since variables refer to objects, if we assign one variable to another, both variables refer to te same object. Because the same list has two different names, ‘l1’ and ‘l2’, we say it is aliased. Meaning: changes made with one alias affect the other. Let’s find out if this is true:

>>> CODE
l1 = [1, 2, 3]
l2 = l1
l1.append(4)
print(l1)
print(l2)
>>> OUTPUT
[1, 2, 3, 4]
[1, 2, 3, 4]

Let’s try a tricky test:

>>> CODE
l1 = [1, 2, 3]
l2 = l1
l1 = l1 + [4]
print(l1)
print(l2)
>>> OUTPUT
[1, 2, 3, 4]
[1, 2, 3]

What happend here? With the previous test, we would have expected the same output: [1, 2, 3, 4]. But this was not the case. Python examines first the second part of the statement ‘l1 + [4]’, before assigning it back to ‘l1’. We are actually re-defining ‘l1’s value, that’s why ‘l1’ and ‘l2’ ended up being not the same object.

You can confirm this with another example involving the type() function:

>>> CODE
a = [1, 2, 3, 4]
print(id(a))
a = a + [5]
print(id(a))
>>> OUTPUT
47850513379592
47850513862024

Clearly: a has been re-defined as a new object.

But wait for this test: codying “a = a + [5]” is the same as codying “a += [5]”?

>>> CODE
a = [1, 2, 3]
print(id(a))
a += [4]
print(id(a))
>>> OUTPUT
47943101571400
47943101571400

As I said, through out this article: Python first analizes the second part of the statement and then assigns it to the first part. In this case, it only alters the value of the variable, it re-defines the value, not the object itself.

Let’s upgrade these tests and try functions.

>>> CODE
def increment(n):
n += 1

a = 1
increment(a)
print(a)
>>> OUTPUT
1

Now this is confusing. Shouldn’t the output be 2 instead of 1?

The thing is, we are not passing the object itself to the function when called, only its value. What I’m implying is that the variable ‘a’ referencing the object isn’t changed, but at the same time the object is being changed within the function. Like a local variable. That’s why the change isn’t seen after all.

Let’s try that, but with a list:

>>> CODE
def increment(n):
n.append(4)

l = [1, 2, 3]
increment(l)
print(l)
>>> OUTPUT
[1, 2, 3, 4]

Bet your head is exploding right now.

The difference here, is that we aren’t referencing a value, we are not assigning a new value to the variable inside the function. We’re using the append() method, which adds a single item to the existing list. This method does not return a new list of items, but will modify the original list by adding the new item (4 in this case) to the end of the list.

You may say: “But what if you reassing a variables value, with another variable?”:

>>> CODE
def assign_value(n, v):
n = v

l1 = [1, 2, 3]
l2 = [4, 5, 6]
assign_value(l1, l2)
print(l1)
>>> OUTPUT
[1, 2, 3]

As we are not applying a method here, it works the same as with the “n += 1” test. It just doesn’t.

Lists really are a funny thing, indeed. With mood swings, you could say. We tried changing their value, succeeded in some cases, failed in others. And if we tried copying them with a method what would happen?

>>> CODE
my_list = [1, 2, 3]
print(my_list)
new_list = my_list.copy()print(my_list)
print(new_list)
print(new_list == my_list)
print(new_list is my_list)
>>> OUTPUT
[1, 2, 3]
[1, 2, 3]
[1, 2, 3]
True
False

From what we can understand from the code: we created a copy of the list, but only that. But we learned form the previous test that methods could alter lists. It would be fair to say, if we made a “copy” it would be an exact copy of the object, therefore the same object under a different name (Stephanie and Lady Gaga all over again).

I mean, from the new test no, but yes in a way. Try adding the bold part in your code:

>>> CODE
my_list = [1, 2, 3]
print(my_list)
new_list = my_list.copy()print(my_list)
print(new_list)
print(new_list == my_list)
print(new_list is my_list)
print(id(my_list[0]))
print(id(new_list[0]))
>>> OUTPUT
[1, 2, 3]
[1, 2, 3]
[1, 2, 3]
True
False
9079040
9079040

We’ve got now two items from each of the lists (which we already know have the same value: 1), and checked for their id’s, and turned out to have the same, meaning those items (objects) are pointing to the same memory address, therefore, pointing to the same object.

This .copy() method creates a shallow copy of the list: meaning it creates a new list with the same references points for the elements within the list.

Now: let’s talk about tuples:

Tuples are the real drama queens around the Python world. I still don’t get them in full, if I’m being honest. So, we’ll try to understand them together. SO MUCH FUN!

What are tuples? Tuples are a built -in data type (just as lists and dictionaries) that are used to store multiple items in a single variable. It is simply a collection which is ordered (indexed), unchangeable (drama queen), and allow duplicate values of its items (not such a drama queen, after all).

Why did I create a special case for tuples? They have a special trait: they are immutable, but their values may change. When a tuple holds a reference to any mutable object, such as a list, for example.

Let’s start our tests, how to create a tuple, and how not to:

>>> CODE
a = ()
print(type(a))
b = (1, 2)
print(type(b))
c = (1)
print(type(c))
d = (1, )
print(type(d))
>>> OUTPUT
<class 'tuple'>
<class 'tuple'>
<class 'int'>
<class 'tuple'>

To make this short: NEVER try to create a tuple like variable c. The variable will ignore the parenthesis and create an int.

Let’s confirm equallity in tuple with values:

>>> CODE
a = (1, 2)
b = (1, 2)
print(a is b)
c = ()
d = ()
print(c is d)
>>> OUTPUT
False
True

Same as with lists, when given the same value (not referencing the variable like b = a), it creates two separate tuple variables, with the same value. If we are creating empty tuples, Python takes it as if they are one and the same, but only if they are empty.

Back to creating int Objects.

These set of tests will blow your minds:

You will try to guess how many objects were created, and how many deleted. You will have the answer below just passing your mouse key over the word Answer just like this one (I know it has the ‘http://’, just ignore it and don’t click on it). I’ll explain the why every objects behaves like they do, one you tried to guess in every test.

Test 1:

>>> CODE
a = 1
b = 1

Questions:

  • How many int objects are created by the execution of the first line of the script? Answer.
  • How many int objects are created by the execution of the second line of the script? Answer.

Test 2:

>>> CODE
a = 1024
b = 1024
del a
del b
c = 1024

Questions:

  • How many int objects are created by the execution of the first line of the script? Answer.
  • How many int objects are created by the execution of the second line of the script? Answer.
  • After the execution of line 3, is the int object pointed by a deleted, Yes or No? Answer.
  • After the execution of line 4, is the int object pointed by b deleted, Yes or No? Answer.
  • How many int objects are created by the execution of the last line of the script? Answer.

Test 3:

>>> CODE
print("I")
print("Love")
print("Python")

Question:

  • Before the execution of line 2 (print("Love")), how many int objects have been created and are still in memory? Answer.

How many did you get right? How many wrong?

Test 1 and Test 2 “should” work the same right? Wrong, because, to get a better performance, Python preallocates memory to keep an array of integer objects from -5 to 256 (because these are the most commonly used values in programming), to fast accessing them. This means if two integer objects whose values are the same, from -5 to 256, they are, in fact, the same object. These values get allocated right when you initialize your NSMALLPOSINTS and NSMALLNEGINTS macros.

Now to the point… Immutable vs. Mutable.

Python needs both immutable and mutable basically because:

  • It needs to establish that an object stays the same through out a program while it’s being executed.
  • It also needs other objects to change its size, it is the case with lists, dictionaries and such as I said before.

Hope you learned something today :)

--

--