What are mutable and immutable objects in Python3?

The Smart Snake src: https://goo.gl/images/ybWJo5

Python is an interpreted (interpreted into C) programming language that was written in 1991 by Guido van Rossum that emphasizes simplicity, readability, and flexibility.

Many programmers today use Python3 because of its original implementation in the procedural programming language C. Because of this, it has been coined as CPython.

His Holiness — Guido van Rossum

Note: For much of this article I am going to be comparing Python to C as C is the “first principles” of Python. If you are not familiar with basic C concepts I would recommend you do so either beforehand or Google some of the concepts as they arise :)

Python works well with agile development because it is relatively fast to get an application built and shipped. Moreover, when it comes to changing features in the application it is easier to rework your code to add features for users as compared to other procedural languages. Unlike C where one must compile their updated code each time before testing, editing and testing newly updated code is much faster with Python. Python also offers the benefit of dynamic typing over C since it is a statically typed language. In statically typed languages, when one declares a variable, one must specify the type of variable (i.e. integer, character, array, etc.).

For example:

int phone_number = 4155555555

Now, we can only manipulate the variables with like minded data.

phone_number = 4156666666

For example, this would not work:

phone_number = “string”

Because Python is a dynamically typed language, variable types do not have to be specified per say. A variable name is just a name that can be bound to any type of object. This is the beauty of object orientated programming. Quite easily, you can assign a name to another object of different type without any extra specifications.

For example, these are all perfectly valid in Python3

>>> phone_number = 4155555555

>>> phone_number = [415, 555, 5555]

>>> phone_number = "connors_phone_number"

id() vs. type()

Two of Python’s most notable built-in functions, id() and type(), are some of the most useful functions when it comes to inspecting data. They return a unique, constant integer and the type of an object, respectively. The id of an object represents the identity of the object instance which, in CPython, is the memory address where that object is stored. Therefore, id() is implemented to return the address of the object in memory. This is a lot like using the &operator on a variable to return the address of that variable in your computers memory.

Let’s further examine..

type(object) returns the type of an object and is generally the same object as returned by object.__class__.

Please note that class objects, as shown above, are not safe for using the is operator and it is highly recommended that one use the == comparison instead.

In an object oriented programming language like Python, one can define a new type by defining a new class. Python already has many native built-in classes such as:

  • integers (ex: x = 3)
  • floats (ex: x = 3.14)
  • strings (ex: x = “hi”)
  • lists (ex: x = [1, 2, 3])
  • dictionary (ex: x = {“key”: value})
  • etc.

Mutable vs. Immutable Types

Python built-in types can be split into two main categories — Mutable and Immutable types. Mutability is just a fancy way of specifying if an object can be modified after it has been declared.

Mutable Types

Python’s mutable types include are:

Lists and dictionaries are excellent types to use when you know your user’s data is going to grow over time because of their mutability characteristics. As a result they are very often used in real world business applications.

List objects offer many built-in methods (a function that belongs to an object class and its instances)such as append() and pop() which add and remove list elements from the end of a list.

Let’s walk through ordering food at a restaurant in OOP:

You start off with an empty order:

order = []

Let’s inspect some of these attributes real quick just to make sure we have a list:

>>> type(order)
<class ‘list’>

>>> id(order)

Next, the server comes and we order a few items:

>>> order.append(“steak”, “salad”, “potatoes”)
>>> order
[‘steak’, ‘salad’, ‘potatoes’]

OH WAIT! We don’t want regular potatoes, we want sweet potatoes!

>>> order.pop()
>>> order
[‘steak’, ‘salad’]
>>> order.append("sweet potaters")
>>> order
[‘steak’, ‘salad’, 'sweet potaters']
>>> id(order)

Object Orientated Deliciousness!

As you can see, we are able to run methods on the list. This does not return a new list, but mutates (changes) the original list. In the end, our two calls to id(cart) return the same integer proving cart to be the same list before and after list method invocations. This is a hallmark of mutable types in Python.

Immutable Types

Some of Python’s include:

  • integers (ex: x = 3)
  • floats (ex: x = 3.14)
  • strings (ex: x = “Hello”)
  • tuples (ex: x = (20, 4))
  • ranges (ex: range(6)
  • booleans (ex: x = True)

Immutable types do not support modifications, hence the name immutable. So, when immutable types are changed (string concatenation for example) Python will return a new instance of a string object to be assigned to a variable name.

Link to demo: https://goo.gl/F4z3nM

Result from PythonTutor.com

In conclusion, id(string) changes after we concatenate our string due to the immutable nature of the object itself. If it were in fact mutable, id(string) wouldn’t be a different number.

You might be saying to yourself — “ok, but what if I just update the index of that string kind of like in.. C..”

Not 🚨 so 🚨 fast 🚨 we 🙅‍♂️ can’t 🚨 do 🚨 that 🚨 dude!
>>> name = "guido"
>>> name[0] = 'G'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment

From above we can see that this is not supported by an immutable object and we get a TypeError that is a reminder that ‘str’ objects are immutable and do not support item assignment.

Pay Attention Here

This is a huge deal because Python treats mutable and immutable objects differently. If you don’t keep track of the qualities of both, it might lead you to unexpected bugs. Take the following example:

>>> account_balance = 1000000
>>> balance_to_transfer = account_balance
>>> account_balance = 2000000
>>> balance_to_transfer

Obviously banking systems aren’t this simple but you get the point… if you don’t know the types you are working with you could make some serious errors in your applications that could lead to being costly.

To make things more confusing take this example for instance. Note: remember that lists are mutable objects whereas integers (above example) are not.

Let’s say we have some software system in a grocery store that keeps track of two distinct lists of SKUs new_items and perished_items:

>>> new_items = ['sku0001', 'sku0002', 'sku0003']
>>> perished_items = ['sku1000', 'sku2000', 'sku3000']
>>> perished_items = new_items
>>> perished_items
>>> ['sku0001', 'sku0002', 'sku0003']
>>> perished_items[0] = 'sku1111'
>>> new_items
['sku1111', 'sku0002', 'sku0003']

You can go here for the step-by-step preview: https://goo.gl/CweUTB
but essentially here is a rundown of what just happened:

results of misunderstanding your types from PythonTutor.com
  • We set the list variable perished_items equal to new_items at some point in the code (this would be a bug unless it was intentional for some reason to create a copy and use it somewhere but the programmer forgot that it was declared or something)
  • Next, when we modified the first element in our perished_items list (which could easily happen by the way from some nefarious function in the program somewhere), it also modified our variable in new_items because the perished_items had created a clone of the original list, new_items.
  • This unexpected result has to do with type list’s mutability.

Let’s walk through what is happening step by step in the code.

In the bank example above:

>>> account_balance = 1000000
>>> balance_to_transfer = account_balance
>>> account_balance = 2000000
>>> balance_to_transfer
  • When account_balance = 1000000 runs, the object 1000000is created, and the variable name account_balance is associated with this object from the global frame.
  • Next, balance_to_transfer = account_balance sets a pointer from balance_to_transfer to the same 1000000 object that has a pointer from account_balance pointing at it.

In C this is just normal behavior but in Python it is known as aliasing

  • Upon running account_balance = 2000000 the integer object 2000000 is created, and that account_balance pointer will point at that same value.
  • The namespace balance_to_transfer is still associated with that 1000000 value and remains unchanged when account_balance is reassigned, so the variable balance_to_transfer returns the value 1000000 .

As you can see if this were a real banking system you’d have some pretty angry high roller clients and bugs in this software system could mean losing a very important user

But what about that grocery store software example before?

  • new_items = [‘sku0001’, ‘sku0002’, ‘sku0003’] which is a list type object is created and new_items points to this mutable list object.

perished_items = new_items sends a pointer from perished_items to the same list object [‘sku0001’, ‘sku0002’, ‘sku0003’]

If you click on the link in the first example of this grocery store software system you’d see this visually but to show logical proof here is a simple example you can run:

>>> id(new_items) == id(perished_items)
>>> new_items is perished_items
  • is tests for object identity which returns True if they are in fact the same object
  • == is different though because it is testing the value of the two objects!
  • Because these two names refer to the same list, when we modify one of them perished_items[0] = sku1111, the change is made to the single list in memory but reflected in both variable names.

Editors note: I hope that you enjoyed my essay on this topic! If you’d like to read more of my work please follow me.

If you want to reach out on social media Twitter is your best bet! -@connorbrer