Copier une liste de listes en Python : copy ou deepcopy

Ta copie Python modifie encore la matrice d'origine ? Suis les références, copie les lignes au bon niveau et vérifie une transformation sans mutation.

PythonPar
Publié le

Tu fais une copie de ta matrice avant de la modifier. Pourtant, une valeur change aussi dans l'original. Ajouter un autre .copy() au hasard ne dit pas quel objet reste partagé : il faut suivre les indices jusqu'à la liste que ton code modifie.

En bref

Pour copier une liste de listes d'entiers en Python, utilise [ligne.copy() for ligne in original] si tu veux modifier les cellules sans toucher à l'original. original.copy(), original[:] et list(original) créent seulement une nouvelle liste externe : ses lignes restent partagées. Si les cellules contiennent elles-mêmes des listes à modifier, une copie récursive avec copy.deepcopy(original) peut être nécessaire.

Pourquoi copy modifie encore la liste originale ?

La copie superficielle duplique le conteneur externe, puis y place des références vers les mêmes éléments. Ici, ces éléments sont des listes. Les deux matrices ont donc des lignes communes.

partage.pyÀ éviter
original = [[1, 2], [3, 4]]copie = original.copy()print(copie is original)print(copie[0] is original[0])copie[0][0] = 9print(original)print(copie)
Terminal
$ python3 partage.pyFalseTrue[[9, 2], [3, 4]][[9, 2], [3, 4]]

Le premier False confirme qu'une liste externe a été créée. Le True suivant explique la fuite : copie[0] et original[0] désignent la même ligne. L'affectation finale remplace une case dans cette ligne partagée.

Deux accès vers la même première ligne après la modification

Pile (stack)

Python
listoriginal[0]
ligne
listcopie[0]
ligne

Tas (heap)

lignelist
9021

Le schéma isole la première ligne. Les conteneurs externes sont distincts ; ce sont leurs éléments qui se rejoignent. Avec copie = original, il n'y aurait même pas de nouveau conteneur : les deux noms désigneraient la même matrice.

Remplacer une ligne ou modifier une cellule, quelle différence ?

Remplacer une ligne change une case du conteneur externe. Modifier une cellule, ou ajouter une valeur à une ligne, agit dans l'objet imbriqué.

remplacement.py
original = [[1, 2], [3, 4]]copie = original.copy()copie[0] = [8, 8]print(original)print(copie)copie[1].append(6)print(original)
Terminal
$ python3 remplacement.py[[1, 2], [3, 4]][[8, 8], [3, 4]][[1, 2], [3, 4, 6]]

La première ligne de copie est remplacée par une nouvelle liste. Celle d'original reste intacte. La deuxième ligne n'a pas été remplacée : l'ajout de 6 y apparaît des deux côtés.

Avant de choisir une copie, pose la question sur l'opération exacte. Réordonner les lignes d'un nouveau conteneur et modifier leur contenu n'ont pas les mêmes besoins. Pour les pièges liés aux suppressions, vois aussi supprimer des éléments pendant une boucle.

Comment copier chaque ligne d'une matrice d'entiers ?

Construis une nouvelle liste externe et une nouvelle liste pour chaque ligne. Une compréhension fait les deux en une expression.

lignes.py
original = [[1, 2], [3, 4]]copie = [ligne.copy() for ligne in original]copie[0][0] = 9copie[1].append(6)print(original)print(copie)print(copie[0] is original[0])
Terminal
$ python3 lignes.py[[1, 2], [3, 4]][[9, 2], [3, 4, 6]]False

[ligne[:] for ligne in original] convient aussi. Pour une matrice d'entiers, recopier les lignes suffit : changer une cellule remplace un entier dans une liste propre à la copie. L'entier lui-même n'est pas modifiable.

Cette expression fonctionne aussi avec des lignes vides ou de longueurs différentes. Elle ne force pas une forme rectangulaire et ne valide pas le type des cellules. Si ton contrat impose une matrice rectangulaire, ce contrôle est une question distincte.

Quand faut-il utiliser deepcopy ?

Une copie de chaque ligne ne sépare pas les listes qui se trouvent encore à l'intérieur des cellules. Une copie récursive permet de séparer ces listes imbriquées de celles de l'original.

profondeur.py
from copy import deepcopy
original = [[[1]]]lignes = [ligne.copy() for ligne in original]lignes[0][0].append(2)copie = deepcopy(original)copie[0][0].append(3)print(original)print(lignes)print(copie)print(copie[0][0] is original[0][0])
Terminal
$ python3 profondeur.py[[[1, 2]]][[[1, 2]]][[[1, 2, 3]]]False

Le 2 atteint l'original parce que la cellule contient encore une liste partagée. Le 3 reste dans la copie récursive. Pour ces listes ordinaires, la profondeur de l'objet modifié décide de ce qu'il faut séparer.

Ne choisis pas deepcopy juste parce qu'un objet est imbriqué. Si tu reconstruis déjà toutes les lignes et remplaces leurs entiers, une copie récursive supplémentaire n'apporte rien au contrat. Les objets de classes personnalisées peuvent définir leur propre comportement de copie ; les exemples ici portent sur des listes et des entiers.

Pourquoi plusieurs lignes changent même après deepcopy ?

deepcopy sépare la copie de l'original, mais peut conserver un partage interne. La répétition [[0, 0]] * 2 crée deux références vers une seule ligne. La copie récursive retrouve cette ligne deux fois et réutilise sa copie.

repetition.py
from copy import deepcopy
original = [[0, 0]] * 2copie = deepcopy(original)copie[0][0] = 7print(original)print(copie)print(copie[0] is copie[1])print(copie[0] is original[0])
Terminal
$ python3 repetition.py[[0, 0], [0, 0]][[7, 0], [7, 0]]TrueFalse

Les deux dernières réponses parlent de relations différentes : les lignes de la copie restent liées entre elles, mais elles ne sont plus la ligne de l'original. Une copie récursive ne répare donc pas automatiquement la construction d'une matrice.

Si chaque ligne doit évoluer indépendamment, crée-la séparément dès le départ :

creation.py
grille = [[0, 0] for _ in range(2)]grille[0][0] = 7print(grille)print(grille[0] is grille[1])
Terminal
$ python3 creation.py[[7, 0], [0, 0]]False

Pour une matrice d'entiers existante dont les lignes doivent être séparées, la compréhension qui recopie chaque ligne crée aussi une ligne par occurrence, même si deux occurrences désignaient auparavant le même objet.

Comment vérifier une fonction qui ne doit pas modifier son entrée ?

Vérifie le résultat attendu et l'état de l'entrée après l'appel. L'égalité == compare les valeurs ; is permet de vérifier un partage d'objet. Une égalité de valeurs avant la modification ne suffit pas à prouver l'indépendance.

Cette transformation ajoute 1 à chaque entier en construisant de nouvelles lignes :

contrat.py
def ajouter_un(grille):    return [[valeur + 1 for valeur in ligne] for ligne in grille]
original = [[8, 1], [], [-2]]resultat = ajouter_un(original)print(resultat)print(original)print(resultat[0] is original[0])
Terminal
$ python3 contrat.py[[9, 2], [], [-1]][[8, 1], [], [-2]]False

Teste aussi l'entrée vide, les lignes vides et deux positions qui partagent une même ligne. Pour vérifier l'indépendance après le retour, modifie une cellule du résultat puis contrôle l'entrée. Le contrat doit préciser si les objets contenus doivent être indépendants ou si seule l'absence de mutation pendant l'appel est exigée.

Python

mirror_rows

4-bit

Inverse chaque ligne d'une matrice sans changer l'ordre des lignes ni modifier l'entrée. L'exercice vérifie le résultat et inclut un cas qui contrôle la matrice d'origine après l'appel.

Ouvrir l'exercice

Pour une transformation qui change l'ordre des éléments selon plusieurs règles, continue avec le tri Python à plusieurs critères.

Questions fréquentes

list(original) copie-t-il aussi les sous-listes ?

Non. Il crée un nouveau conteneur externe avec les mêmes éléments. Si ces éléments sont des listes, elles restent partagées.

Pourquoi une copie superficielle suffit-elle pour une liste d'entiers ?

Les entiers sont immuables. Remplacer une case dans la nouvelle liste ne modifie pas la case de l'ancienne. Pour une liste de listes, il faut aussi séparer les listes dans lesquelles tu comptes écrire.

deepcopy crée-t-il des lignes toutes différentes ?

Pas si plusieurs positions désignent la même ligne dans l'entrée. Il conserve ce partage dans la copie. Crée ou recopie chaque ligne séparément si le contrat exige des lignes indépendantes.

Dois-je utiliser deepcopy pour inverser des lignes d'entiers ?

Non si tu construis déjà de nouvelles lignes contenant les entiers dans l'ordre voulu. Le point à vérifier est que l'opération ne modifie pas les lignes de l'entrée.

Sources

À toi d'écrire le code.