La copie d'une liste

Avec un simple =

Utilisation de 'is'

Python dispose d'une instruction 'is' qui permet de tester si deux noms de variables désignent le même objet.

Le code suivant :


L = [2,5,8]
M = L

print(M is L)

M = [5,7]
print(M is L)

donne :

True
False

L'affectation M = L a pour conséquence que M et L désignent le même objet (ici la même liste). Ce qui explique la première réponse (True).
Cette affectation est suivie d'une réaffectation de M : M = [5,7] qui a naturellement pour conséquence que M et L ne désignent plus le même objet, d'où la seconde réponse (False).

Avec les objets, nous disposons de méthodes sur ces objets. Ces méthodes ne correspondent pas en général à une réaffectation de l'objet mais à une modification de l'objet lui-même (sur place, c'est à dire sans changer l'adresse mémoire).

Utilisons par exemple la méthode append() qui permet d'ajouter une valeur à une liste :


L = [2,5,8]
M = L

print("Est-ce que M et L désignent le même objet ? ", M is L)

M.append(4)
print('Nous ajoutons un élément à la liste M par la méthode append().')
print("Est-ce que M et L désignent encore le même objet ? ", M is L)

print()
print('Valeur de M : ', M)
print('Et plus surprenant au premier abord, valeur de L :', L)

Résultat de l'exécution de ce code :

Est-ce que M et L désignent le même objet ?  True
Nous ajoutons un élément à la liste M par la méthode append().
Est-ce que M et L désignent encore le même objet ?  True

Valeur de M :  [2, 5, 8, 4]
Et plus surprenant au premier abord, valeur de L : [2, 5, 8, 4]

Vous constatez que L est modifiée par la modification de M : M et L désignent le même objet et comme la méthode append() modifie l'objet lui-même (sur place, à son adresse de départ), toute modification de M modifie L (et vice-versa).

Différence entre 'is' et '=='

Pour bien saisir ce qui précède, il est important de comprendre que 'is' et '==' n'ont pas le même rôle.

Illustration :


L = [2,5,8]
M = L

X = [2,5,8]



print("Est-ce que M et L désignent le même objet ? ", M is L)
print("Est-ce que X et L désignent le même objet ? ", X is L)

print()

print("Est-ce que les contenus de M et L sont les mêmes :", M == L)
print("Est-ce que les contenus de X et L sont les mêmes :", X == L)

donne :

Est-ce que M et L désignent le même objet ?  True
Est-ce que X et L désignent le même objet ?  False

Est-ce que les contenus de M et L sont les mêmes : True
Est-ce que les contenus de X et L sont les mêmes : True

M et L désignent le même objet, au même emplacement mémoire. Il s'agit de deux noms différents pour un même objet. Tandis que X et L désignent deux listes différentes, ayant les mêmes contenus.

Pour bien comprendre le principe, on peut imaginer que les valeurs des variables sont rangées dans des boîtes. L'affectation L=[2,5,8] a deux effets : le premier est de créer une boîte dans laquelle la liste de nombres [2,5,8] sera rangée, le deuxième sera de donner un nom à cette boîte : L. On colle en quelque sorte une étiquette à la boîte.
L'affectation qui suit M=L consiste alors simplement à donner un deuxième nom (coller une seconde étiquette, ici M) à cette même boîte.
Tandis que X=[2,5,8] consiste à créer une seconde boîte, avec une étiquette X.
On a donc au final deux boîtes, contenant pour le moment les mêmes nombres. L'une de ces boîtes a deux noms (L et M), l'autre boîte n'a qu'un nom (X). Une modification du contenu de la boîte M modifie le contenu de la boîte L puisqu'il s'agit de deux noms de la même boîte. Tandis qu'une modification du contenu de la boîte X n'aura aucun effet sur le contenu de la boîte L.

On peut également utiliser l'instruction id() qui donne l'identifiant python d'un objet (en première approche, on peut considérer qu'il s'agit de l'adresse de l'objet)


L = [2,5,8]
M = L

X = [2,5,8]



print("Identifiant de L ", id(L))
print("Identifiant de M ", id(M))
print("Identifiant de X ", id(X))

donne (vous aurez sûrement d'autres valeurs numériques en exécutant le code)

Identifiant de L  140217002007624
Identifiant de M  140217002007624
Identifiant de X  140217001175944

Nous confirmons ainsi que M et L désignent le même objet, tandis que X est un autre objet.

Avec copy

Si l'on veut une copie de la liste L en évitant que des modifications de cette copie ne soient également subies par la liste L initiale, une première solution est d'utiliser copy.

Exemple.


from copy import copy

L = [2,5,8]
M = copy(L)

 
print("Identifiant de L ", id(L))
print("Identifiant de M ", id(M))
print('Est-ce que M et L désignent le même objet ?', M is L)
print('Est-ce que M et L ont le même contenu ?', M == L)


print()
print('Une modification de M ne modifie pas L : ')
M.append(45)
print('Liste M : ', M)
print('Liste L : ', L)

On obtient :

Identifiant de L  140527679036552
Identifiant de M  140527679243720
Est-ce que M et L désignent le même objet ? False
Est-ce que M et L ont le même contenu ? True

Une modification de M ne modifie pas L : 
Liste M :  [2, 5, 8, 45]
Liste L :  [2, 5, 8]

Toutefois, on peut avoir des phénomènes comme celui observé ci-dessous :


L = [2,5,8, [9,12]]
M = copy(L)

 
print("Identifiant de L ", id(L))
print("Identifiant de M ", id(M))
print('Est-ce que M et L désignent le même objet ?', M is L)
print('Est-ce que M et L ont le même contenu ?', M==L)
print()

print('Identifiant de L[3] : ', id(L[3]))
print('Identifiant de M[3] : ', id(M[3]))


M[3].append(15)
M.append(45)


print('Modification de M[3] et modification de M.')
print('Liste M : ', M)
print('Liste L : ', L)

qui donne :

Identifiant de L  139653666066184
Identifiant de M  139653692053320
Est-ce que M et L désignent le même objet ? False
Est-ce que M et L ont le même contenu ? True

Identifiant de L[3] :  139653666106504
Identifiant de M[3] :  139653666106504
Modification de M[3] et modification de M.
Liste M :  [2, 5, 8, [9, 12, 15], 45]
Liste L :  [2, 5, 8, [9, 12, 15]]

Ainsi M et L ne désignent pas le même objet, mais ces deux listes contiennent en position d'indice 3 un objet commun ! Une modification de l'objet L[3] est donc aussi une modification de l'objet M[3].

Avec list() ou en compréhension

L'effet obtenu est le même qu'avec copy(). L'avantage est qu'on n'utilise pas de module externe, on peut préférer ces méthodes si on ne manipule pas de listes de listes ou d'autres choses subtiles.

Les trois codes suivants produisent donc le même effet.


from copy import copy

L = [2,5,8]
M = copy(L)

L = [2,5,8]
M = list(L)

L = [2,5,8]
M = [ x for x in L]

Avec deepcopy

Si l'on veut éviter que les objets éléments de L ne deviennent objets de M lors de la copie, on utilisera une copie profonde au lieu d'une copie simple.

Illustration.


from copy import deepcopy

L = [2,5,8, [9,12]]
M = deepcopy(L)

 
 
print("Identifiant de L ", id(L))
print("Identifiant de M ", id(M))
print('Est-ce que M et L désignent le même objet ?', M is L)
print('Est-ce que M et L ont le même contenu ?', M==L)
print()

print('Identifiant de L[3] : ', id(L[3]))
print('Identifiant de M[3] : ', id(M[3]))


M[3].append(15)
M.append(45)


print('Modification de M[3] et modification de M.')
print('Liste M : ', M)
print('Liste L : ', L)

On constate cette fois que les objets contenus dans les deux listes ne sont pas partagés par nos deux listes :

Identifiant de L  140054649130760
Identifiant de M  140054649168392
Est-ce que M et L désignent le même objet ? False
Est-ce que M et L ont le même contenu ? True

Identifiant de L[3] :  140054649171080
Identifiant de M[3] :  140054649171016
Modification de M[3] et modification de M.
Liste M :  [2, 5, 8, [9, 12, 15], 45]
Liste L :  [2, 5, 8, [9, 12]]