#Python: Les références partagées et le module copy

Cet article est archivé dans la rubrique  Le langage Python.

D’aucuns pensent que la manière la plus simple de copier une liste est de déclarer une variable et de lui affecter ladite liste comme dans l’exemple ci-dessous:

liste_initiale = [1, 6.3, ['Alphonse', 'Solange']]
copie_liste = liste_initiale
print(copie_liste)

Résultat : [1, 6.3, [‘Alphonse’, ‘Solange’]]

Mais en faisant cela, nous avons simplement créé un alias, c’est-à-dire que nous avons instancié un nouvel objet qui partage la même référence que la liste copiée! Pour nous en convaincre, visualisons le code avec Python Tutor.

capture_1

Nous constatons de visu que les deux variables référencent la même liste et que l’indice 2 de celle-ci référence la petite liste « enchassée » dans la grande liste. C’est clair comme de l’eau de vie.

Ça veut dire quoi? Ça veut dire tout simplement que nous avons créé une FAKE LISTE! Si je modifie une valeur dans copie_liste, je modifie également cette valeur dans liste_initiale puisque c’est la même référence! Essayons sans plus attendre.

liste_initiale = [1, 6.3, ['Alphonse', 'Solange']]
copie_liste[1] = "FAKE"
print(liste_initiale)

Résultat : [1, ‘FAKE’, [‘Alphonse’, ‘Solange’]]

capture_2

Faire une shallow copy avec list() ou liste[:]

Essayons de voir si nous pouvons solutionner le problème en déclarant une nouvelle liste et en lui affectant liste_initiale. Deux possiblités s’offrent à nous :

  • avec list()
liste_initiale = [1, 6.3, ['Alphonse', 'Solange']]
copie_liste = list(liste_initiale)
print(copie_liste)
  • avec liste[:]
liste_initiale = [1, 6.3, ['Alphonse', 'Solange']]
copie_liste = liste_initiale[:]
print(copie_liste)

capture_list

Que remarquons-nous? Cette fois-ci, chaque variable référence sa propre liste. Ça veut dire que si je modifie copie_liste[1] qui correspond à la valeur 6.3, je ne vais pas modifier liste_initiale[1]. Essayons voir…

list_2

ÇA MARCHE! Finalement, ce n’était pas bien compliqué! Du coup, je vais remplacer « Alphonse » par « Gustave » dans copie_liste.

copie_liste[2][0] = "Gustave"
print(copie_liste)
print(liste_initiale)

copie_liste = [1, « FANTASTIC », [‘Gustave’, ‘Solange’]

liste_initiale = [1, 6.3, [‘Gustave’, ‘Solange’]

Eh! Mais… what’s happens??? 😦 Its looks like again a FAKE LIST! Isn’t it? Gustave squatte les deux listes? Quel sombre programmeur se cache derrière cette sorcellerie?

Eh bien, il nous suffit de visualiser encore une fois notre code pour constater que les deux variables référencent, certes, deux listes différentes mais que le deuxième indice de ces deux listes référence la même petite liste! Du coup, Gustave possède le don d’ubiquité…

En fait, nous avons fait une shallow copy, c’est-à-dire une copie superficielle. Nous avons seulement copié les éléments de premier niveau. Ça n’est pas un problème lorsque ceux-ci sont des objets immuables (1, 6.3) mais ça le devient lorsque ce sont des objets mutables (liste, dictionnaire…)

capt

Avec le code ci-dessous, on arrive certes à faire une copie profonde mais ce n’est pas l’idéal.

liste_initiale = [1, 6.3, ['Alphonse', 'Solange']]
copie_liste = list(liste_initiale)
copie_liste[2] = list(liste_initiale[2])
copie_liste[2][0] = 'Gustave'

capt_2

Le module copy

Le module copy qui fonctionne avec tous les types mutables, permet de faire deux sortes de copies différentes:

  • la shallow copy (shallow signifie superficiel ou peu profond.)
  • la deep copy (deep signifie profond.)

Pour utiliser ces deux méthodes, il faut importer le module copy:

  • copy.copy effectue une shallow copy
  • copy.deepcopy effectue une deep copy

une shallow copy effectue une copie des éléments de premier niveau. Elle copie les éléments 1 et 8.6 tandis que le troisième élément (qui est une liste) devient une référence partagée.  Cela signifie que si vous remplacez « Alphonse » par « Gustave » dans la petite liste qui se trouve à l’indice 2, cette modification affectera liste_initiale car ce n’est pas un élément de premier niveau.

import copy
liste_initiale = [1, 8.6, ['Alphonse', 'Solange']]
copie_liste = copy.copy(liste_initiale)

shallow_copy

Une deep copy effectue une copie en profondeur, c’est-à dire qu’elle ne va pas se contenter de copier seulement les éléments de premier niveau mais tous les éléments quelque soit leur profondeur. Par exemple, si je déclare une liste qui contient une liste qui contient elle-même une liste et que j’utilise deep copy, tout sera dupliqué. Même Gustave et Solange qui se trouvent dans la liste de troisième niveau seront clonés!

import copy
liste_gigogne = [1, 8.6, ['Alphonse', 'Loana', ['Solange', 'Gustave']]]
copie_liste = copy.deepcopy(liste_gigogne)

deepcop

Le module copy (avec ses méthodes shallow copy et deep copy) fonctionne parfaitement avec d’autres objets mutables tels que les dictionnaires.

import copy
dico = {"Nom" : "Chafouin", 'Prénoms' : {'Prénom 1' : "Bernadette", 'Prénom 2' : "Solange"}}
copie_dico = copy.deepcopy(dico)
copie_dico['Nom'] = "Grossin"
copie_dico['Prénoms']['Prénom 1'] = "Corinne"

dico

Avec les objets de type str(), int() ou bien float(), il est inutile d’utiliser le module copy puisque de toute façon,  l’affectation va créer un nouvel objet qui aura sa propre référence.

a = 3
b = a
nom = "chêne"
arbre = nom

str

Avec un tuple, le module copy ne sert à rien. Un tuple est une collection d’éléments immuable. Faire une copie ne créé rien d’autre qu’une référence partagée. Toutefois, il existe une combine pour copier un tuple mais elle ne présente aucun intérêt si ce n’est d’encombrer inutilement la mémoire… Cette combine, c’est la concaténation.

tuple_initial = (1, 2, 3)
copie_tuple = ()
copie_tuple += tuple_initial

tup

Conclusion

Python est un langage de programmation où tout est objet. Cette particularité offre une grande modularité mais cela se fait au prix d’une plus lourde occupation de mémoire. C’est la raison pour laquelle Python minimise la création d’objets grâce aux références partagées. Ces dernières permettent de réduire le nombre d’objets conservés en mémoire.

Lorsqu’une copie s’avère nécessaire, Python met à disposition le module copy qui permet d’effectuer des copies superficielles (shallow copy) ou profondes (deep copy).

Publicités

Auteur : Ordinosor

Bienvenue sur Miamondo, mon blog personnel. "Mia mondo", c'est de l'espéranto et ça signifie "Mon monde" en français. Je m'appelle Benoît alias Ordinosor, Français expatrié en Allemagne. Mes centres d'intérêt sont les distributions GNU/Linux, le langage de programmation Python, la science-fiction et l'espéranto.

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s