Python Language
Mutable vs immuable (et lavable) en Python
Recherche…
Mutable vs immuable
Il existe deux types de types dans Python. Types immuables et types mutables.
Immuables
Un objet d'un type immuable ne peut pas être modifié. Toute tentative de modification de l'objet entraînera la création d'une copie.
Cette catégorie comprend: les entiers, les flottants, les complexes, les chaînes, les octets, les tuples, les plages et les frozensets.
Pour mettre en évidence cette propriété, jouons avec l' id
builtin. Cette fonction renvoie l'identificateur unique de l'objet passé en paramètre. Si l'identifiant est le même, c'est le même objet. Si cela change, alors c'est un autre objet. (Certains disent que c'est en fait l'adresse mémoire de l'objet, mais méfiez-vous d'eux, ils sont du côté obscur de la force ...)
>>> a = 1
>>> id(a)
140128142243264
>>> a += 2
>>> a
3
>>> id(a)
140128142243328
Ok, 1 n'est pas 3 ... Dernières nouvelles ... Peut-être pas. Cependant, ce comportement est souvent oublié en ce qui concerne les types plus complexes, en particulier les chaînes de caractères.
>>> stack = "Overflow"
>>> stack
'Overflow'
>>> id(stack)
140128123955504
>>> stack += " rocks!"
>>> stack
'Overflow rocks!'
Aha! Voir? Nous pouvons le modifier!
>>> id(stack)
140128123911472
Bien qu'il semble que nous puissions changer la chaîne nommée par la stack
variables, ce que nous faisons réellement, c'est de créer un nouvel objet pour contenir le résultat de la concaténation. Nous sommes dupes parce que dans le processus, le vieil objet ne va nulle part, donc il est détruit. Dans une autre situation, cela aurait été plus évident:
>>> stack = "Stack"
>>> stackoverflow = stack + "Overflow"
>>> id(stack)
140128069348184
>>> id(stackoverflow)
140128123911480
Dans ce cas, il est clair que si nous voulons conserver la première chaîne, nous avons besoin d’une copie. Mais est-ce si évident pour d'autres types?
Exercice
Maintenant, sachant comment fonctionnent les types immuables, que diriez-vous avec le morceau de code ci-dessous? Est-ce sage?
s = ""
for i in range(1, 1000):
s += str(i)
s += ","
Mutables
Un objet de type mutable peut être modifié et modifié in situ . Aucune copie implicite n'est faite.
Cette catégorie comprend: les listes, les dictionnaires, les séries et les ensembles.
Continuons à jouer avec notre petite fonction d' id
.
>>> b = bytearray(b'Stack')
>>> b
bytearray(b'Stack')
>>> b = bytearray(b'Stack')
>>> id(b)
140128030688288
>>> b += b'Overflow'
>>> b
bytearray(b'StackOverflow')
>>> id(b)
140128030688288
(En remarque, j'utilise des octets contenant des données ascii pour clarifier mon propos, mais souvenez-vous que les octets ne sont pas conçus pour contenir des données textuelles. La force peut me pardonner.)
Qu'avons-nous? Nous créons un bytearray, le modifions et en utilisant l' id
, nous pouvons nous assurer qu'il s'agit du même objet, modifié. Pas une copie
Bien sûr, si un objet doit être modifié souvent, un type mutable fait un travail bien meilleur qu'un type immuable. Malheureusement, la réalité de cette propriété est souvent oubliée lorsqu'elle fait le plus mal.
>>> c = b
>>> c += b' rocks!'
>>> c
bytearray(b'StackOverflow rocks!')
D'accord...
>>> b
bytearray(b'StackOverflow rocks!')
Waiiit une seconde ...
>>> id(c) == id(b)
True
Effectivement. c
n'est pas une copie de b
. c
est b
.
Exercice
Maintenant, vous comprenez mieux quel effet secondaire un type mutable implique, pouvez-vous expliquer ce qui ne va pas dans cet exemple?
>>> ll = [ [] ]*4 # Create a list of 4 lists to contain our results
>>> ll
[[], [], [], []]
>>> ll[0].append(23) # Add result 23 to first list
>>> ll
[[23], [23], [23], [23]]
>>> # Oops...
Mutable et immuable comme arguments
L'un des principaux cas d'utilisation lorsqu'un développeur doit prendre en compte la mutabilité est lorsqu'il transmet des arguments à une fonction. Ceci est très important, car cela déterminera la capacité de la fonction à modifier des objets qui n'appartiennent pas à sa portée, c'est-à-dire si la fonction a des effets secondaires. Ceci est également important pour comprendre où le résultat d'une fonction doit être rendu disponible.
>>> def list_add3(lin):
lin += [3]
return lin
>>> a = [1, 2, 3]
>>> b = list_add3(a)
>>> b
[1, 2, 3, 3]
>>> a
[1, 2, 3, 3]
Ici, l'erreur est de penser que lin
, en tant que paramètre de la fonction, peut être modifié localement. Au lieu de cela, lin
et a
référence le même objet. Comme cet objet est mutable, la modification est effectuée sur place, ce qui signifie que l'objet référencé à la fois par lin
et a
est modifié. lin
n'a pas vraiment besoin d'être retourné, car nous avons déjà une référence à cet objet sous la forme d' a
. a
et b
finissent par référencer le même objet.
Cela ne va pas la même chose pour les tuples.
>>> def tuple_add3(tin):
tin += (3,)
return tin
>>> a = (1, 2, 3)
>>> b = tuple_add3(a)
>>> b
(1, 2, 3, 3)
>>> a
(1, 2, 3)
Au début de la fonction, tin
et a
référence le même objet. Mais c'est un objet immuable. Ainsi , lorsque la fonction tente de le modifier, l' tin
recevoir un nouvel objet avec la modification, en a
conserve une référence à l'objet d' origine. Dans ce cas, le retour de l' tin
est obligatoire ou le nouvel objet serait perdu.
Exercice
>>> def yoda(prologue, sentence):
sentence.reverse()
prologue += " ".join(sentence)
return prologue
>>> focused = ["You must", "stay focused"]
>>> saying = "Yoda said: "
>>> yoda_sentence = yoda(saying, focused)
Note: reverse
fonctionne sur place.
Que pensez-vous de cette fonction? At-il des effets secondaires? Le retour est-il nécessaire? Après l'appel, quelle est la valeur de saying
? De focused
? Que se passe-t-il si la fonction est appelée à nouveau avec les mêmes paramètres?