Formation I.S.N.

Cloner une liste

=

Quel sera l'affichage en exécutant le code ci-dessous ?



Qu'est ce qui explique le résultat affiché pour L ?

Le comportement observé est-il différent du comportement observé dans le code qui suit ?



  • une aide technique
  • explication

Utilisez l'opérateur is qui permet de savoir si deux étiquettes désignent le même objet.

Par exemple :


L = [2,3,4]
M = L
print(M is L)

Dans ce cas, l'affichage est True : M et L sont deux noms différents pour le même objet.

Résultats observés

Le résultat pour le premier code est le suivant :


[50, 2, 3]
[50, 2, 3]

Ce qu'il faut remarquer avant tout est que la liste L a également été modifiée par la modification de M[0].

Pourtant dans un code tel que :


L = 2
M = L
M = 50
print(M)
print(L)

L a bien encore la valeur 2 à la fin de l'exécution.

Le comportement n'est pas différent

Pour autant, le comportement n'est pas différent. Nous ne comparons pas ici deux situations analogues.

Le code :


L = 2
M = L
print(M is L)
M = 50
print(M is L)

et le code :


L = [1,2,3]
M = L
print(M is L)
M = [1,2,3]
print(M is L)

renvoient tous deux en premier lieu True et en second False. Ce qui signifie que dans les deux cas, l'affectation M = L a simplement pour effet de donner une nouvelle étiquette au même objet. Tandis que le code M = 2 ou le code M = [1,2,3] a pour effet de créer un nouvel objet (nouvel objet qui a le même contenu que le premier objet créé).

Des schémas

Quelques schémas peuvent aider à comprendre la situation.
Après le code :


L = 2
M = L

l'état est :

int, état1

Et après le code :


L = 2
M = L # l'effet de cette ligne est éliminé du fait de l'instruction suivante !
M = 2

l'état est :

int, état2

Après le code :


L = [1,2,3]
M = L

l'état est le suivant :

état avec listes >

et après le code :


L = [1,2,3]
M = L # l'effet de cette ligne est éliminé du fait de l'instruction suivante !
M = [1,2,3]

l'état est le suivant :

état avec listes

Retour au premier script

Dans le cas du premier script, on commence par :


L = [1,2,3]
M = L

ce qu'on l'on peut schématiser par :

état avec listes
M désigne le même objet que L. M et L doivent être considérés comme deux étiquettes d'un même objet et non comme deux objets distincts. C'est l'adresse de la liste qui est copiée et non sa valeur lors de l'exécution du code M = L. De ce fait, toute modification de M est aussi une modification de L (et vice-versa).

Une affectation telle que M[0] = 50 ne change pas l'adresse de la liste M mais change uniquement le contenu de la cellule d'indice 0.

Après l'instruction M[0] = 50, l'état de la mémoire est donc :

état avec listes

On peut se représenter une liste en mémoire comme une série de cases mémoires contiguẽs et les étiquettes L et M comme des pointeurs vers la première case mémoire :

état avec listes

On comprend dès lors que la modification de M[0] ou L[0] concerne le contenu de la même case mémoire et que M et L pointent toujours sur la même zone mémoire.

== et is

Analyser les différences de rôle entre == et is à l'aide du script suivant:



  • explication
  • is permet de savoir si deux objets sont les mêmes.
  • == permet de savoir si deux objets ont les mêmes contenus.

Conséquence :

  • Lorsque is renvoie True, == renvoie donc nécessairement True.
  • Lorsque is renvoie False, == renvoie True ou False suivant que les contenus sont égaux ou non.

un clone ?

On aimerait faire que M soit une copie de L et non le même objet.

Le script suivant est-il satisfaisant ?


def copieListe(liste) :
	return [ x for x in liste]
	
L = [2, 3, 4, 5]
M = copieListe(L) 
print("Contenu de la liste L : " , L)
print("Contenu de la liste M : " , M)

M[0] = 222
print("Contenu de la liste L après modification de M : " , L)
print("Contenu de la liste M  après modification de M : " , M)
  • réponse
  • variantes

Il semble que le code convienne en observant les résultats puisque la modification de M effectuée laisse L inchangée.

Mais observez le résultat du script suivant :


def copieListe(liste) :
	return [ x for x in liste]
	
L = [2, [10,11,12], 4, 5]
M = copieListe(L) 
print("Contenu de la liste L : " , L)
print("Contenu de la liste M : " , M)

M[1][0] = 222
print("Contenu de la liste L après modification de M : " , L)
print("Contenu de la liste M  après modification de M : " , M)

On obtient :

Contenu de la liste L :  [2, [10, 11, 12], 4, 5]
Contenu de la liste M :  [2, [10, 11, 12], 4, 5]
Contenu de la liste L après modification de M :  [2, [222, 11, 12], 4, 5]
Contenu de la liste M  après modification de M :  [2, [222, 11, 12], 4, 5]

On observe en effet avec le code suivant :


def copieListe(liste) :
	return [ x for x in liste]
	
L = [2, [10,11,12], 4, 5]
M = copieListe(L) 
print("M is L = " , M is L)
print("M[1] is L[1] : " , M[1] is L[1])

que M n'est pas l'objet L mais que M[1] et L[1] par contre désignent bien le même objet. En effet, M[1] a été défini par une instruction équivalente à M[1] = L[1] et nous avons vu, que de cette façon, nous ne faisions que créer une nouvelle étiquette vers le même objet (vous pouvez également vérifier que M[0] is L[0] retourne True).

Vous pourrez vérifier que les résultats seront les mêmes en réécrivant la fonction de copie de liste de la façon suivante :


def copieListe(liste) :
	M = []
	for i in range(len(liste)) : 
		M.append( liste[i] )
	return M

ou encore :


def copieListe(liste) :
	M = []
	for x in liste  : 
		M.append( x )
	return M

ou encore :


def copieListe(liste) :
	M = [ 0 for i in range(len(liste)) ]
	for i in range(len(liste))  : M[i] = liste[i]
	return M

ou , plus pythonique :


def copieListe(liste) :
	return liste[:]

ou même avec la fonction copy du module copy :


from copy import copy 
 
L = [2, [10,11,12], 4, 5]
M = copy(L) 

copie profonde

Vérifiez avec quelques exemples que les problèmes soulevés dans le corrigé de l'exercice 3 sont réglés avec la fonction deepcopy du module copy.

Écrire une fonction python récursive de copie de liste en profondeur. En d'autres termes, tentez de réaliser votre propre fonction deepcopy.

  • utilisation de deepcopy
  • une deepcopy perso

On est vite convaincu par quelques exemples.

Ci-dessous, on modifie des listes contenues dans M mais L reste bien inchangée :


from copy import deepcopy 
  
L = [2, [10,11,12], 4, 5, [2,[3,4,[5]]] ]
M = deepcopy(L) 
 
M[1][0] = 222
M[4][1][2][0] = 333
print(L)
print(M)

On pourra remarquer avec le script ci-dessous :


from copy import deepcopy 
  
L = [2, [10,11,12], 4, 5]
M = deepcopy(L) 
print("M is L = " , M is L)
print("M[1] is L[1] : " , M[1] is L[1])
print("M[0] is L[0] : " , M[0] is L[0])

que L[0] et M[0] désignent encore le même objet. Mais cela n'est pas gênant : sur un int, cela ne peut avoir les mêmes effets éventuellement fâcheux que nous avons observés précédemment.

Une proposition :


def  maCopieProfondeur(L) :
     if isinstance(L, list) : return [ maCopieProfondeur(x) for x in L ]
     else : return L
  
L = [2, [10,11,12], 4, 5, [2,[3,4,[5]]] ]
M = maCopieProfondeur(L) 
 
M[1][0] = 222
M[4][1][2][0] = 333
print(L)
print(M)