Apprenez en lisant dans l'ordre

Types mutables et immuables

Apprends la distinction entre types mutables et immuables en Python pour éviter les bugs liés au partage de référence.

Pourquoi tu dois apprendre ça

Cet article n'est pas destiné aux grands débutants, mais le sujet est incontournable si tu veux vraiment comprendre la programmation en profondeur. C'est une histoire classique : un débutant tombe sur un bug lié à la mutabilité et passe des heures à déboguer.

En Python, tu peux croire que y = x relie simplement deux variables avec un signe égal, et découvrir que modifier l'une réécrit aussi l'autre. C'est ça, la mutabilité.

Ce chapitre éclaircit la différence entre « mutable » (modifiable) et « immuable » (non modifiable), et te montre comment copier en toute sécurité.

Classification des types — à quel camp chacun appartient
Immuableint, floatstr, bool, tupleUn change,l'autre nonMutablelist, dictsetUn change,les deux aussiincluttraitincluttrait

Un type qui permet de réécrire la variable elle-même (via append, une affectation d'élément, update, etc.) est mutable ; celui qui ne le permet pas est immuable.

list / dict / set sont mutables, et tout le reste (int / float / str / bool / tuple) est immuable.

Types immuables — modifier l'un n'affecte pas l'autre

Avec un type immuable (un type non modifiable), une fois que tu as passé la valeur avec y = x, modifier x ensuite n'a aucun effet sur y.

Par exemple, après x += 1, x vaut 11, mais y reste à la valeur d'origine 10.

Comment se déroule l'affectation pour un type immuable
x = 10y = xx += 1x = 11y = 10exécuterésultat
# int est immuable
x = 10
y = x
x += 1
print(x)   # 11
print(y)   # 10  <- inchangé

# str est immuable aussi
x = "hello"
y = x
x = x + " world"
print(x)   # hello world
print(y)   # hello

# tuple est immuable aussi
x = ("a", "b")
y = x
x = ("c", "d")
print(x)   # ('c', 'd')
print(y)   # ('a', 'b')

Avec les types immuables, une fois qu'une valeur est enregistrée dans une autre variable, modifier la variable d'origine n'affecte pas la copie.

① Copie un prix de produit price = 1000 dans une autre variable old_price, puis augmente le prix avec price = price + 500. Affiche price et old_price.

② Copie un tuple de tags tags = ("sale", "new") dans old_tags, puis réaffecte tags à un autre tuple avec tags = ("limited", "gift"). Affiche tags et old_tags.

(Réussis les deux parties et l'explication s'affichera.)

Éditeur Python

Exécuter le code pour voir le résultat

Les variables Python sont des « étiquettes posées sur des boîtes »

Une variable en Python ne contient pas la valeur elle-même — c'est plutôt une étiquette qui pointe vers une donnée (une boîte) en mémoire. Quand tu écris y = x, Python ne crée pas une nouvelle boîte ; il colle juste une autre étiquette (y) sur la même boîte que celle vers laquelle x pointe.

- Types immuables : une réaffectation comme x = 11 déplace simplement l'étiquette x sur une autre boîte. y pointe toujours vers la boîte d'origine, donc sa valeur reste indépendante.

- Types mutables : une opération en place comme x.append(...) modifie la boîte partagée elle-même, donc le changement est visible aussi à travers y.

C'est la frontière entre immuable et mutable.

Types mutables — avec y = x, changer l'un change l'autre

Avec un type mutable (list / dict / set), en revanche, écrire y = x laisse y et x pointer sur la même boîte.

Si tu fais ensuite une opération qui réécrit le contenu, comme x.append(...), le changement apparaît aussi à travers y.

Affectation d'un type mutable — partage de la même boîte
x=[a,b]y=xx.append(c)x=[a,b,c]y=[a,b,c]exécuteles deux

y = x ne crée pas de copie séparée — les deux noms pointent au même endroit.

Les opérations qui réécrivent le contenu, comme append, modifient la boîte partagée à laquelle les deux étiquettes font référence, donc le changement est visible à travers y aussi.

# list est mutable
x = ["a", "b"]
y = x          # ajoute juste une autre étiquette y à la même liste

x.append("c")  # réécrit le contenu directement
print(x)       # ['a', 'b', 'c']
print(y)       # ['a', 'b', 'c']  <- y a grandi aussi !

# remove se comporte pareil
x.remove("b")
print(y)       # ['a', 'c']  <- disparaît de y aussi

# dict et set montrent le même comportement
d = {"k": 1}
e = d
e["new"] = 99
print(d)       # {'k': 1, 'new': 99}  <- d a grandi aussi

Pourquoi le partage est-il le comportement par défaut ?

Si y = x copiait tout le contenu à chaque fois, alors quand, par exemple, x contient des millions d'enregistrements récupérés d'une base de données, la mémoire et le temps d'exécution exploseraient vite.

En Python, un nom de variable n'est pas la valeur elle-même — c'est une étiquette qui pointe vers un emplacement en mémoire.

Tu ne peux pas changer ce mécanisme, donc c'est à toi (celui qui écrit) de l'utiliser avec prudence.

Voyons le partage que les types mutables provoquent.

① Crée un panier de courses cart = ["lait", "pain"], puis écris old_cart = cart (dans l'intention d'en faire une copie).

② En pensant n'ajouter "œuf" qu'à cart avec cart.append("œuf"), affiche ensuite cart et old_cart.

Les deux devraient contenir "œuf". C'est le piège du jour.

Éditeur Python

Exécuter le code pour voir le résultat

Utilise copy() pour obtenir une version indépendante

Quand tu veux garder les données d'origine et créer une version séparée, utilise la méthode copy().

Écrire y = x.copy() copie le contenu dans une nouvelle boîte et la donne à y, donc les modifications ultérieures de x n'affectent pas y.

copy() crée une « boîte séparée »
x=[a,b]y=x.copy()x.append(c)x=[a,b,c]y=[a,b]exécuteindépendant

y = x.copy() alloue à ce moment-là une nouvelle zone mémoire.

Ensuite, réécrire le contenu de x avec x.append(...) ou similaire n'a aucun effet sur y.

# list / dict / set ont tous .copy()
x = ["apple", "lemon"]
y = x.copy()

x.append("grape")
print(x)   # ['apple', 'lemon', 'grape']
print(y)   # ['apple', 'lemon']  <- pas affecté

# dict.copy() fonctionne pareil
d = {"a": 1, "b": 2}
e = d.copy()
d["c"] = 3
print(d)   # {'a': 1, 'b': 2, 'c': 3}
print(e)   # {'a': 1, 'b': 2}  <- pas affecté

# set.copy() fonctionne pareil
s = {1, 2}
t = s.copy()
s.add(3)
print(s)   # {1, 2, 3}
print(t)   # {1, 2}            <- pas affecté

# list a d'autres façons de copier
x = ["apple", "lemon"]
y1 = list(x)   # passer par le constructeur donne une nouvelle liste
y2 = x[:]      # copie intégrale via slice (du début à la fin)

Attention quand ta liste contient d'autres listes

copy() ne construit qu'une nouvelle boîte extérieure. Les valeurs mutables à l'intérieur (comme des listes dans une liste) restent partagées. On appelle ça une copie superficielle (shallow copy).

Fais attention avec les données imbriquées.

Corrigeons le bug de la section précédente avec copy().

Crée cart = ["lait", "pain"], mais cette fois écris old_cart = cart.copy(). Exécute cart.append("œuf"), puis affiche cart et old_cart. « œuf » ne devrait pas finir dans old_cart cette fois.

Éditeur Python

Exécuter le code pour voir le résultat

Dans cet article, tu as appris la différence entre types mutables et immuables, et comment copy() te donne une version indépendante.

L'idée qu'un nom de variable n'est pas la valeur elle-même mais une étiquette qui pointe vers un emplacement en mémoire se retrouve aussi dans d'autres langages, pas seulement Python. Quand tu manipules des types mutables, glisse un copy().

QUIZ

Vérification des connaissances

Répondez à chaque question une par une.

Question 1Parmi ces groupes, lequel ne contient que des types mutables ?

Question 2Quelle est la valeur de b après l'exécution du code suivant ?

``
a = [1, 2, 3]
b = a
a.append(4)

Question 3Tu veux garder le contenu de la liste d'origine a et obtenir une version indépendante b. Quelle est la façon la plus appropriée de l'écrire ?