How arguments are passed to functions and what does that imply for mutable and immutable objects

All the data in a Python code is represented by objects or by relations between objects. Every object has an identity, a type, and a value.

An object’s identity never changes once it has been created. You may think of it as the object’s address in memory. The is and is not operator compares the identity of two objects. The id() function returns an integer representing its identity.

>>> a = 1
>>> id(a)
10105088

An object’s type defines the possible values and operations (e.g. “does it have a length?”) that type supports. The type() function returns the type of an object. An object type is unchangeable like the identity.

>>> a = 1
>>> type(a)
<class 'int'>

Every variable holds an object instance that’ll later on and during initiation will be assigned. so what’s the difference between mutable and immutable?

  • mutable objects : are objects that could be changed like “ list, set, dict”
  • immutable objects: are unchangeable objects like “int, float, bool, str, tuple, unicode”

Let’s start by comparing the tuple (immutable) and list (mutable) data types. From both data types, we can access elements by index and we can iterate over them.

The main difference is that a tuple cannot be changed once it’s defined.

>>> l1 = [1, 2, 3]
>>> s1 = (4, 5, 6)
>>> print(l1[0])
1
>>> print(s1[0])
4
>>> l1[0] = 100
>>> print(l1[0])
100
>>> s1[0] = 400
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment

For mutable objects, the id is passed and conserved when changing the underlying value

>>> l1 = [1, 2, 3]
>>> l1 = l2
>>> id(l1)
140180307719752
>>> id(l2)
140180307719752
>>> l1[0] = 100
>>> l1
[100, 2, 3, 4]
>>> id(l1)
140180307719752
>>> id(l2)
140180307719752
>>> l2
[100, 2, 3, 4]

So, when we have changed the values of the firstvariable, the values of the second one are also changed. This happens only with the mutable objects.

Because the same list has two different names, l1 and l2, we say that it is aliased. Changes made with one alias affect the other.

You can see how you can prevent this with created new list or using slicing :

>>> l1 = [1, 2, 3]
>>> id(l1)
140180307719752
>>> l2 = l1[:]
>>> id(l2)
140180307719048
>>> l1[0] = 100
>>> l1
[100, 2, 3]
>>> id(l1)
140180307719752
>>> l2
[1, 2, 3]
>>> id(l2)
140180307719048

When expanding list(mutable) vs tuple(immutable), the list retain the same id, when the tuple does not :

>>> l1 = [1, 2, 3]
>>> s1 = (1, 2, 3)
>>> id(l1)
140180340307784
>>> id(s1)
140180347307712
>>> l1 += [4, 5, 6]
>>> s1 += (4, 5, 6)
>>> id(l1)
140180340307784
>>> id(s1)
140180347263592

For immutable objects, the id, when creating 2 variables representing the same object, can be the same (for strings for example) or different (for tuple for example):

>>> a = "Test"
>>> b = "Test"
>>> id(a)
140180339994896
>>> id(b)
140180339994896
>>> s1 = (1, 2)
>>> s2 = (1, 2)
>>> id(s1)
140180339957064
>>> id(s2)
140180307779400
>>>

Every time when we try to update the value of an immutable object, a new object is created instead :

>>> id(a)
10105088
>>> a += 1
>>> id(a)
10105120
>>> a = "Test"
>>> id(a)
140180339994896
>>> a = "Test" + "Test"
>>> id(a)
140180347327024

Why does it matter and how differently does Python treat mutable and immutable objects

Now we know the difference between mutable and immutable objects we know that immutable objects can be used to make sure that the object remains constant throughout the program. How ever the mutable objects can be changed whether expected or not. Changing a mutable data type won’t change it’s memory address on the other hand changing an immutable type can produce errors.

  • Immutable are quicker to access than mutable objects.
  • Mutable objects are great to use when you need to change the size of the object, example list, dict etc.. Immutables are used when you need to ensure that the object you made will always stay the same.
  • Immutable objects are fundamentally expensive to “change”, because doing so involves creating a copy. Changing mutable objects is cheap.

Amusing fact, in Python, upon startup, Python3 keeps an array of integer objects, from -5 to 256. For example, for the int object, marcos called NSMALLPOSINTS and NSMALLNEGINTS are used: this means that when you create an int from the range of -5 and 256, you are actually referencing to the existing object.

How arguments are passed to functions and what does that imply for mutable and immutable objects

Its important to know the difference between mutable and immutable types and how they are treated when passed onto functions. Memory efficiency is highly affected when the proper objects are used.

For example if a mutable object is called by reference in a function, it can change the original variable itself. Hence to avoid this, the original variable needs to be copied to another variable. Immutable objects can be called by reference because its value cannot be changed anyways.

>>> def increment(n):
... n += 1
...
>>> a = 3
>>> increment(a)
>>> a
3
>>> def increment(l):
... l += [4]
...
>>> l = [1, 2, 3]
>>> increment(l)
>>> l
[1, 2, 3, 4]