Objets mutables vs immuables en Python - Un guide visuel et pratique

Python est un langage génial. En raison de sa simplicité, de nombreuses personnes le choisissent comme premier langage de programmation.

Les programmeurs expérimentés utilisent également Python tout le temps, grâce à sa large communauté, à l'abondance de packages et à une syntaxe claire.

Mais il y a un problème qui semble dérouter les débutants ainsi que certains développeurs expérimentés: les objets Python. Plus précisément, la différence entre les objets mutables et immuables .

Dans cet article, nous approfondirons notre connaissance des objets Python, apprendrons la différence entre les objets mutables et immuables et verrons comment nous pouvons utiliser l' interpréteur pour mieux comprendre le fonctionnement de Python.

Nous utiliserons des fonctions et des mots-clés importants tels que idet is, et nous comprendrons la différence entre x == yet x is y.

Êtes-vous prêt pour cela? Commençons.

En Python, tout est un objet

Contrairement à d'autres langages de programmation où le langage prend en charge les objets, en Python, tout est vraiment un objet - y compris les entiers, les listes et même les fonctions.

Nous pouvons utiliser notre interprète pour vérifier que:

>>> isinstance(1, object) True >>> isinstance(False, object) True def my_func(): return "hello" >>> isinstance(my_func, object) True

Python a une fonction intégrée, idqui renvoie l'adresse d'un objet en mémoire. Par exemple:

>>> x = 1 >>> id(x) 1470416816

Ci-dessus, nous avons créé un objet sous le nom de xet lui avons attribué la valeur 1. Nous avons ensuite utilisé id(x)et découvert que cet objet se trouve à l'adresse 1470416816en mémoire.

Cela nous permet de vérifier des choses intéressantes sur Python. Disons que nous créons deux variables en Python - une par le nom de x, et une par le nom de y- et leur affectons la même valeur. Par exemple, ici:

>>> x = "I love Python!" >>> y = "I love Python!"

Nous pouvons utiliser l'opérateur d'égalité ( ==) pour vérifier qu'ils ont bien la même valeur aux yeux de Python:

>>> x == y True

Mais s'agit-il du même objet en mémoire? En théorie, il peut y avoir ici deux scénarios très différents.

Selon le scénario (1) , nous avons en réalité deux objets différents, un par le nom xet un autre par le nom de y, qui se trouvent avoir la même valeur.

Pourtant, il se peut également que Python stocke ici un seul objet, qui a deux noms qui le référencent - comme indiqué dans le scénario (2) :

Nous pouvons utiliser la idfonction présentée ci-dessus pour vérifier ceci:

>>> x = "I love Python!" >>> y = "I love Python!" >>> x == y True >>> id(x) 52889984 >>> id(y) 52889384

Comme nous pouvons le voir, le comportement de Python correspond au scénario (1) décrit ci-dessus. Même si x == ydans cet exemple (c'est-à-dire xet yont les mêmes valeurs ), ce sont des objets différents en mémoire. En effet id(x) != id(y), comme nous pouvons le vérifier explicitement:

>>> id(x) == id(y) False

Il existe un moyen plus court de faire la comparaison ci-dessus, et c'est d'utiliser l' isopérateur Python . Vérifier si x is yc'est la même chose que vérifier id(x) == id(y), ce qui signifie si xet ysont le même objet en mémoire:

>>> x == y True >>> id(x) == id(y) False >>> x is y False

Cela met en lumière la différence importante entre l'opérateur d'égalité ==et l'opérateur d'identité is.

Comme vous pouvez le voir dans l'exemple ci-dessus, il est tout à fait possible que deux noms en Python ( xet y) soient liés à deux objets différents (et donc, x is yest False), où ces deux objets ont la même valeur (ainsi x == yest True).

Comment pouvons-nous créer une autre variable qui pointe vers le même objet que celui xvers lequel pointe? Nous pouvons simplement utiliser l'opérateur d'affectation =, comme ceci:

>>> x = "I love Python!" >>> z = x

Pour vérifier qu'ils pointent bien vers le même objet, nous pouvons utiliser l' isopérateur:

>>> x is z True

Bien sûr, cela signifie qu'ils ont la même adresse en mémoire, comme nous pouvons le vérifier explicitement en utilisant id:

>>> id(x) 54221824 >>> id(z) 54221824

Et, bien sûr, ils ont la même valeur, nous nous attendons donc x == zà revenir Trueégalement:

>>> x == z True

Objets mutables et immuables en Python

Nous avons dit que tout en Python est un objet, mais il existe une distinction importante entre les objets. Certains objets sont mutables tandis que certains sont immuables .

Comme je l'ai déjà mentionné, ce fait est source de confusion pour de nombreuses personnes qui ne connaissent pas Python, nous allons donc nous assurer que c'est clair.

Objets immuables en Python

Pour certains types en Python, une fois que nous avons créé des instances de ces types, elles ne changent jamais. Ils sont immuables .

Par exemple, les intobjets sont immuables en Python. Que se passera-t-il si nous essayons de changer la valeur d'un intobjet?

>>> x = 24601 >>> x 24601 >>> x = 24602 >>> x 24602

Eh bien, il semble que nous avons changé xavec succès. C'est exactement là que beaucoup de gens sont confus. Que s'est-il passé exactement sous le capot ici? Utilisons idpour étudier plus en profondeur:

>>> x = 24601 >>> x 24601 >>> id(x) 1470416816 >>> x = 24602 >>> x 24602 >>> id(x) 1470416832

Ainsi, nous pouvons voir qu'en attribuant x = 24602, nous n'avons pas changé la valeur de l'objet qui xavait été lié auparavant. Au contraire, nous avons créé un nouvel objet et lui avons lié le nom x.

Donc, après avoir assigné 24601à xen utilisant x = 24601, nous avions l'état suivant:

Et après utilisation x = 24602, nous avons créé un nouvel objet et lié le nom xà ce nouvel objet. L'autre objet avec la valeur de 24601n'est plus accessible par x(ou par tout autre nom dans ce cas):

Whenever we assign a new value to a name (in the above example - x) that is bound to an int object, we actually change the binding of that name to another object.

The same applies for tuples, strings (str objects), and bools as well. In other words, int (and other number types such as float), tuple, bool, and str objects are immutable.

Let's test this hypothesis. What happens if we create a tuple object, and then give it a different value?

>>> my_tuple = (1, 2, 3) >>> id(my_tuple) 54263304 >>> my_tuple = (3, 4, 5) >>> id(my_tuple) 56898184

Just like an int object, we can see that our assignment actually changed the object that the name my_tuple is bound to.

What happens if we try to change one of the tuple's elements?

>>> my_tuple[0] = 'a new value' Traceback (most recent call last): File "", line 1, in  TypeError: 'tuple' object does not support item assignment

As we can see, Python doesn't allow us to modify my_tuple's contents, as it is immutable.

Mutable objects in Python

Some types in Python can be modified after creation, and they are called mutable. For example, we know that we can modify the contents of a list object:

>>> my_list = [1, 2, 3] >>> my_list[0] = 'a new value' >>> my_list ['a new value', 2, 3]

Does that mean we actually created a new object when assigning a new value to the first element of my_list? Again, we can use id to check:

>>> my_list = [1, 2, 3] >>> id(my_list) 55834760 >>> my_list [1, 2, 3] >>> my_list[0] = 'a new value' >>> id(my_list) 55834760 >>> my_list ['a new value', 2, 3]

So our first assignment my_list = [1, 2, 3] created an object in the address 55834760, with the values of 1, 2, and 3:

We then modified the first element of this list object using my_list[0] = 'a new value', that is - without creating a new list object:

Now, let us create two names – x and y, both bound to the same list object. We can verify that either by using is, or by explicitly checking their ids:

>>> x = y = [1, 2] >>> x is y True >>> id(x) 18349096 >>> id(y) 18349096 >>> id(x) == id(y) True

What happens now if we use x.append(3)? That is, if we add a new element (3) to the object by the name of x?

Will x by changed? Will y?

Well, as we already know, they are basically two names of the same object:

Since this object is changed, when we check its names we can see the new value:

>>> x.append(3) >>> x [1, 2, 3] >>> y [1, 2, 3]

Note that x and y have the same id as before – as they are still bound to the same list object:

>>> id(x) 18349096 >>> id(y) 18349096

In addition to lists, other Python types that are mutable include sets and dicts.

Implications for dictionary keys in Python

Dictionaries (dict objects) are commonly used in Python. As a quick reminder, we define them like so:

my_dict = {"name": "Omer", "number_of_pets": 1}

We can then access a specific element by its key name:

>>> my_dict["name"] 'Omer'

Dictionaries are mutable, so we can change their content after creation. At any given moment, a key in the dictionary can point to one element only:

>>> my_dict["name"] = "John" >>> my_dict["name"] 'John'

It is interesting to note that a dictionary's keys must be immutable:

>>> my_dict = {[1,2]: "Hello"} Traceback (most recent call last): File "", line 1, in  TypeError: unhashable type: 'list'

Why is that so?

Let's consider the following hypothetical scenario (note: the snippet below can't really be run in Python):

>>> x = [1, 2] >>> y = [1, 2, 3] >>> my_dict = {x: 'a', y: 'b'}

So far, things don't seem that bad. We'd assume that if we access my_dict with the key of [1, 2], we will get the corresponding value of 'a', and if we access the key [1, 2, 3], we will get the value 'b'.

Now, what would happen if we attempted to use:

>>> x.append(3)

In this case, x would have the value of [1, 2, 3], and y would also have the value of [1, 2, 3]. What should we get when we ask for my_dict[[1, 2, 3]]? Will it be 'a' or 'b'? To avoid such cases, Python simply doesn't allow dictionary keys to be mutable.

Taking things a bit further

Let's try to apply our knowledge to a case that is a bit more interesting.

Below, we define a list (a mutable object) and a tuple (an immutable object). The list includes a tuple, and the tuple includes a list:

>>> my_list = [(1, 1), 2, 3] >>> my_tuple = ([1, 1], 2, 3) >>> type(my_list)  >>> type(my_list[0])  >>> type(my_tuple)  >>> type(my_tuple[0]) 

So far so good. Now, try to think for yourself – what will happen when we try to execute each of the following statements?

(1) >>> my_list[0][0] = 'Changed!'

(2) >>> my_tuple[0][0] = 'Changed!'

In statement (1), what we are trying to do is change my_list's first element, that is, a tuple. Since a tuple is immutable, this attempt is destined to fail:

>>> my_list[0][0] = 'Changed!' Traceback (most recent call last): File "", line 1, in  TypeError: 'tuple' object does not support item assignment

Note that what we were trying to do is not change the list, but rather – change the contents of its first element.

Let's consider statement (2). In this case, we are accessing my_tuple's first element, which happens to be a list, and modify it. Let's further investigate this case and look at the addresses of these elements:

>>> my_tuple = ([1, 1], 2, 3) >>> id(my_tuple) 20551816 >>> type(my_tuple[0])  >>> id(my_tuple[0]) 20446248

When we change my_tuple[0][0], we do not really change my_tuple at all! Indeed, after the change, my_tuple's first element will still be the object whose address in memory is 20446248. We do, however, change the value of that object:

>>> my_tuple[0][0] = 'Changed!' >>> id(my_tuple) 20551816 >>> id(my_tuple[0]) 20446248 >>> my_tuple (['Changed!', 1], 2, 3)

Since we only modified the value of my_tuple[0], which is a mutable list object, this operation was indeed allowed by Python.

Recap

In this post we learned about Python objects. We said that in Python everything is an object, and got to use id and is to deepen our understanding of what's happening under the hood when using Python to create and modify objects.

We also learned the difference between mutable objects, that can be modified after creation, and immutable objects, which cannot.

We saw that when we ask Python to modify an immutable object that is bound to a certain name, we actually create a new object and bind that name to it.

We then learned why dictionary keys have to be immutable in Python.

Understanding how Python "sees" objects is a key to becoming a better Python programmer. I hope this post has helped you on your journey to mastering Python.

Omer Rosenbaum, Swimm’s Chief Technology Officer. Cyber training expert and Founder of Checkpoint Security Academy. Author of Computer Networks (in Hebrew). Visit My YouTube Channel.