Skip to content

Mutable and Immutable Types in Python

Learn whether a variable can change (mutable) or not (immutable). Mutable: List, Dict, Set. Immutable: String, Int, Float, Bool, Tuple, Range, Decimal.

Mutability and immutability in Python (and in other programming languages): variables that can change (mutable) or not (immutable). Can’t the content of any variable change, unless it is “final” (that is more of a Java thing)? Yes, being “final” means the variable is a constant and will never change during the execution of the program. In reality, the value of variables “under the hood” changes for some and not for others, even though it looks like they all change.

Note on the languages this applies to: Although here I will show Python examples, this applies in a very similar way, or exactly the same, in most (if not “all”) languages, such as Java, JavaScript, PHP, etc.

Code on GitHub (I recommend reading the article first and then downloading/cloning the code at the end): View on GitHubInvarato/JarrobaPython / mutables_e_inmutables

Before we start, let’s look at a very simple Python example to discover some details:

mi_texto = "un texto"

We can change the value of the variable by adding something more:

mi_texto = mi_texto + " y algo más"

So when we print the variable to the screen:

print(mi_texto)

It will show:

un texto y algo más

Since the String value of the variable mi_texto can be changed, I ask you: is String mutable? Well, no, String is Immutable, because in RAM the previously stored “un texto” has not been extended. Instead, it has been copied together with the addition of ” y algo más” to store the whole “un texto y algo más” somewhere else in RAM, and the variable will point to the last stored value. That is, a String is always created anew (immutable) even though we think it is being modified (a false belief of mutability). Let’s look at this in detail.

Immutables

Immutables are the easiest to use in programming (they are usually the simple data types: String, Integer, Boolean, etc.) because they do exactly what you expect them to do at every moment. Paradoxically, to work this way they are the ones that punish memory the most (they are not optimized to take up less memory when copied, and they wear out the memory more because they are written more times).

An immutable object once created, as its name says, cannot change its state once it has been created. But I have changed String or int values many times after creating them, haven’t I? Apparently, but nothing really changes, and I’ll explain it next (I suggest that as you read each of the next paragraphs you look at the image below it; in the following images, you read first from the left the “Python Console” with a green arrow indicating the added line of code, and then the right part with the memory and its occupants).

When we declare a variable (in the following image “mi_variable”) in Python (or any other programming language), a variable is created in memory at a memory address (in the image it will be stored at memory address “id: 0”). If we then assign to it (the variable is a cursor or pointer that “points to the memory address of its value”) an immutable value, which for this example will be a text (String, in the image “algo, algo más”), it will be created at another memory location (in the image at memory address “id: 1”); then the variable will point to the immutable object that holds its value.

Memory diagram: mi_variable (Id 0) points to a String object "algo, algo más" (Id 1)

Although I could do a simple example, here I’m going to show you two cases of immutability (deep down they are the same thing, but this first case clears up many concepts). The first case of immutability, and the one I think is most important because it’s usually the one that confuses people most, is when an immutable value (of a variable) is passed as a parameter to a function. When the immutable value is passed as a parameter to a function (in the image, to the variable “var_de_func” at memory address “Id:2”), that value is immediately copied to another memory address (in the image “Id: 3”); so you can deduce that if it is modified inside the function, you have to extract it (usually with “return” inside the function) to be able to use it outside the function. Therefore, what happens inside the function stays inside the function (as the saying goes, “what happens in Vegas stays in Vegas” 😛 ) and won’t affect variables outside the function (unless it is returned with “return”).

Memory diagram: passing an immutable to a function copies the value and var_de_func points to the copy (Id 3)

The second case of immutability is not as noticeable and goes more unnoticed. This is when an object with an immutable value is modified, what really happens is that it is copied and the modification is stored at another memory address (in the image “Id: 4”), making the variable point to this new object and deleting the object that nobody points to (normally the deletion is done by garbage collectors, which look for objects that nobody points to and free that memory).

Memory diagram: the function modifies the String and var_de_func points to a new object (Id 4), leaving the original untouched

This way, if at the end we print the value stored in “mi_variable”, it will return what is stored at the memory location it points to (which is what was there at the beginning, since it has not been touched):

Memory diagram: print(mi_variable) shows "algo, algo más", the unchanged original value

Or if we print what is inside the function, it would print what the variable “var_de_func” points to:

Memory diagram: print(var_de_func) inside the function shows "algo, algo más, añadido"

In short, an immutable value will never change once stored at a memory location, and if it has to be used, it is copied.

Memory location identifier in Python

In Python we can use the built-in function “id()” to get the identifier of the object of the variable we pass to it. This identifier number returned by “id()” is the address of the object in memory (each memory location is unique, so it can be used as a unique identifier, which is what Python does).

mi_texto = "un texto"

identificador = id(mi_texto)

print("Id de mi_texto: {}".format(identificador))

It will print the memory location it occupies, in my case:

Id de mi_texto: 31818126

More information about this method at: Built-in Functions (Python docs)

Immutable code example

Try it yourself:

if __name__ == "__main__":
    mi_variable_inmutable = "algo, algo más"
    print("mi_variable_inmutable (id: {}): {}".format(id(mi_variable_inmutable), mi_variable_inmutable))

    def mi_funcion(var_de_func):
        print("var_de_func antes de retornar (id: {}): {}".format(id(var_de_func), var_de_func))
        var_de_func += ", añadido"
        print("var_de_func (id: {}): {}".format(id(var_de_func), var_de_func))
        return var_de_func

    retorno_de_funcion = mi_funcion(mi_variable_inmutable)
    print("retorno_de_funcion (id: {}): {}".format(id(retorno_de_funcion),retorno_de_funcion))

    print("mi_variable_inmutable después de función (id: {}): {}".format(id(mi_variable_inmutable), mi_variable_inmutable))

It prints:

mi_variable_inmutable (id: 48154248): algo, algo más
var_de_func antes de retornar (id: 48154248): algo, algo más
var_de_func (id: 52186528): algo, algo más, añadido
retorno_de_funcion (id: 52186528): algo, algo más, añadido
mi_variable_inmutable después de función (id: 48356248): algo, algo más

Studying the result, especially the memory address given by “id()”, we see that three objects have indeed been created:

  1. At the start of the program, mi_variable_inmutable, which is kept until the end
  2. When it is passed as a parameter to var_de_func
  3. When var_de_func is modified (this last one is then returned in the “return”)

Mutables

Mutables are objects whose state can change in the future once they have been created.

Mutables are the most “complex” to use in programming (they are usually data structures such as dict, list, etc.), and not only because they are more complex since they are structures that hold things, but because they tend to get tangled up with pointers. Paradoxically, they are the ones that harm memory the least (they are written only once and always reused). It must be said that mutables are designed this way on purpose, because copying a whole data structure (although you can) would take a long time and use a lot of memory, probably without making use of the copy (copying a String object is not the same as copying a list with millions of String objects, only to find out you didn’t need the copy; it becomes a waste of processor and memory).

As we did before with Immutables, now we’ll see an example of a Mutable, specifically a list of String values. The variable “mi_variable” will be stored in a memory space (in the image “Id: 0”) and it points to another memory location (in the image “Id: 1”) where our Immutable object of type list will be. To start, we’ll initialize the list with two Strings (in the image it points to “Id: 90” and “Id: 91”).

Memory diagram: mi_variable points to a List object (Id 1) that points to two Strings (Id 90 and 91)

When the mutable value is passed as a parameter to a function (in the image, to the variable “var_de_func” at memory address “Id:2”), the variable will point to the mutable object (in the image “Id: 1”), so it is NOT copied. Here you can already deduce some problems (when the object is modified inside the function, it will be modified outside) and advantages (it doesn’t take up more memory and the “copy” of a gigantic mutable is immediate).

Memory diagram: passing a mutable to a function makes var_de_func point to the same list (Id 1) without copying

Thus, when the mutable object is modified from inside the function, it is modified for everyone (inside and outside the function). In this example we’ll add a new String value to the list.

Memory diagram: the function adds a String (Id 92) to the shared list, changing it outside the function too

If we print mi_variable from outside the function, we’ll see that “someone” has indeed modified our mutable object.

Memory diagram: mi_variable and var_de_func point to the same mutable list, and print outside the function shows the modified value

On the other hand, if we print var_de_func inside the function, since it points to the same object, it will print the same thing.

Memory diagram: print inside the function shows the list that var_de_func and mi_variable both point to, the same one

In short, a mutable value can be changed in the future once stored at a memory location, and if it has to be used, the same object is always used, pointed to from different variables.

Mutable code example

Try it yourself:

if __name__ == "__main__":
  def mi_funcion(var_de_func):
        print("var_de_func (id: {}): {}".format(id(var_de_func), var_de_func))
        var_de_func += ["añadido"]
        print("var_de_func antes de retornar (id: {}): {}".format(id(var_de_func), var_de_func))
        return var_de_func

    mi_variable_mutable = ["algo", "algo más"]
    print("mi_variable_mutable (id: {}): {}".format(id(mi_variable_mutable), mi_variable_mutable))

    retorno_de_funcion = mi_funcion(mi_variable_mutable)
    print("retorno_de_funcion (id: {}): {}".format(id(retorno_de_funcion), retorno_de_funcion))

    print("mi_variable_mutable después de función (id: {}):\t.\t{}".format(id(mi_variable_mutable), mi_variable_mutable))

It will print the memory location it occupies, in my case:

mi_variable_mutable (id: 52183808): ['algo', 'algo más']
var_de_func (id: 52183808): ['algo', 'algo más']
var_de_func antes de retornar (id: 52183808): ['algo', 'algo más', 'añadido']
retorno_de_funcion (id: 52183808): ['algo', 'algo más', 'añadido']
mi_variable_mutable después de función (id: 52183808): ['algo', 'algo más', 'añadido']

Studying the result, especially the memory address given by “id()”, we see that no object has been created other than the first one, and all the modifications have been made on the same object.

Copying the Mutable object to another memory address

First of all, a warning: this is NOT making a mutable object immutable.

It is very easy to copy a mutable object to another memory address, simply by using its constructor (for a list “list()”, for a dictionary “dict()”, for a set “set()”) and passing the mutable object itself as a parameter. For example:

mi_variable_mutable = ["valor 1", "valor 2"]
print("mi_variable_mutable (id: {}): {}".format(id(mi_variable_mutable), mi_variable_mutable))

mi_variable_mutable_copiado = list(mi_variable_mutable)
print("mi_variable_mutable_copiado (id: {}): {}".format(id(mi_variable_mutable_copiado), mi_variable_mutable_copiado))

When printing the result, we can see that while holding the same content, the memory address is different:

mi_variable_mutable (id: 48398336): ['valor 1', 'valor 2']
mi_variable_mutable_copiado (id: 51397696): ['valor 1', 'valor 2']

Python types classified as Immutable and Mutable

Immutables:

  • Strings (Text)
  • Int (Integer number)
  • Float (Floating-point number)
  • Decimal (Decimal number)
  • Complex (Complex number)
  • Bool (Boolean)
  • Tuple: Acts as an immutable list as long as its elements are Hashable.
  • Frozenset (Frozen set): Acts as an immutable set as long as its elements are Hashable
  • Bytes
  • Range
  • None (Null)

Mutables:

  • List: Accepts any type of values
  • Dict (Dictionary): Accepts only Hashable keys and values of any type
  • Set: Accepts only Hashable values
  • Bytearray (array of bytes): Among other uses, it can be used as a mutable String.
  • MemoryView (Memory view): Reference to objects
  • Class defined by the developer. More information in the article about Hashable.

More information about each type in the official documentation at: Built-in Types (Python docs)

And if you want to expand your knowledge about Mutables and Immutables, I recommend you take a look at the article about Hashables.

Bibliography

“Mutable and Immutable Types in Python” by Ramón Invarato at jarroba.com, licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0. Registered at Safe Creative.

Ramón Invarato

I design and build data platforms and intelligent systems, and I enjoy sharing that experience here.

You may also like

Mapping and MutableMapping in Python

Learn how to build your own dictionary-like classes in Python with Mapping and MutableMapping: magic methods, lifecycle, repr, str and a full working example.

Apr 20, 2019

Hashables in Python

A hashable is any object that returns its "Hash Code" when asked, and that code stays the same for the whole life of the object. Learn how it works in Python.

Nov 18, 2018

Comments

Leave a comment


This site uses Akismet to reduce spam. Learn how your comment data is processed.