Copier des listes et des objets en python

Copier des listes et des objets en python

La copie de liste est une erreur courante pour le développeur Python. En effet, le débutant en python qui n’a pas appris un langage comme C ou C++ et qui n’a pas de notions de pointeur, référence et valeur de la mémoire risque de commettre l’erreur suivante lors qu’il a besoin de copier 2 listes.

In [6]: l1 = [1, 2, 3, 4]

In [7]: l2 = l1

In [8]: l1
Out[8]: [1, 2, 3, 4]

In [9]: l2
Out[9]: [1, 2, 3, 4]

Ce qui ne provoque aucune erreur puisque c’est autorisé par le langage et que ça peut avoir un sens.
Cependant, si vous faites des modifications à l1 après l’affectation, la liste l2 sur également modifier :

In [10]: l1 = [1, 2, 3, 4]

In [11]: l2 = l1

In [12]: l2
Out[12]: [1, 2, 3, 4]

In [13]: l1[0] = 1000

In [14]: l2
Out[14]: [1000, 2, 3, 4]

In [15]: l1
Out[15]: [1000, 2, 3, 4]

Ici j’ai « copié » les listes et j’ai modifié la première valeur de la liste L2 a été également modifiée et vaut 1000 :

In [14]: l2
Out[14]: [1000, 2, 3, 4]

Mais qu’est ce que c’est que ce merdier!?!

C’est là qu’il faut faire intervenir la notion de pointeur et la notion de valeur.
NB : attention ici je vais simplifié au maximum

La mémoire de votre ordinateur est un ensemble de cases dans lequel vous allez mettre vos données. C’est un peu un ensemble de boites à chaussure où vous auriez rangé vos chaussures.
Pour accéder à une paire de chaussures particulière, il faut préciser dans quelle boite se trouve cette paire. Imaginons que vous soyez un peu psychopathe sur les bords et que vous auriez numéroté vos boites à chaussures.
Le plus simple pour retrouver vos chaussures, est de chercher le numéro de la boite.
En informatique c’est exactement pareil. Pour accéder à une donnée en mémoire on précise le numéro de la boite à chaussure qu.on nomme « adresse » afin d’accéder à la donnée.

Dans le cas d’un exemple de données placées linéairement en mémoire, c’est-à-dire que les données, d’un tableau par exemple, sont les unes à la suite des autres pour faciliter leur manipulation, il est plus simple de manipuler à partir de la première case mémoire que de manipuler toutes les données concernées.

À ce stade, il faut également comprendre qu’un objet qui possède un certain nombre d’attributs et de placer en mémoire et accessible grâce à un numéro de boite, une adresse.

C’est exactement le cas de notre liste qui est un objet et quand je fais :

l2 = l1

En fait, je ne fais pas une copie de la liste, je crée un deuxième objet qui pointe sur le premier objet.
Par conséquent, toutes les modifications de l’un entrainent une modification de l’autre, car les deux objets pointent sur les mêmes données : « la même boîte à chaussure »

Bon, comment on fait une copie de liste ?!

Pour une liste simple, c’est simple (jolie répétition… Je la garde…), il suffit de dire qu’on veut explicitement une copie de la liste :

l2 = l1[:]

Et là plus de problèmes d’objet qui pointe sur les mêmes données puisse que les données ont été dupliquées. L’exemple ci-dessous illustre le procédé :

In [16]: l1 = [1, 2, 3, 4]

In [17]: l2 = l1[:]

In [18]: l1[0] = 1000

In [19]: l2
Out[19]: [1, 2, 3, 4]

In [20]: l1
Out[20]: [1000, 2, 3, 4]i

Ici seul l1 a été modifié, les données de l2 étant complètement indépendantes…

OK donc c’est simple, vous me direz, il y a que les culs à pieds qui se font prendre…

Euh…, non!!!

Les références aux données et les copies dans les objets

Le même genre de problème peut survenir dans la recopie d’objets possédant des listes, par exemple.

Imaginons que je définisse la classe suivante :

class DataConso(object):
    def __init__(self):
        self.l_conso = []

qui possède donc comme attribut une liste. Le problème de copie de donnée se posera de la même manière si on utilise l’opérateur « = », par exemple :

if __name__ == '__main__':
    maconso = DataConso()
    maconso.l_conso.append(10)
    maconso.l_conso.append(20)
    maconso.l_conso.append(30)
    maconso.l_conso.append(40)

    maconso_copie = maconso
    maconso_copie.l_conso[2] = 100

    print('affichage liste originale : ', maconso.l_conso)
    print('affichage liste modifiée : ', maconso_copie.l_conso)

Dans cet exemple, je mets des valeurs dans la liste définit comme attribut de la classe, puis créer un nouvel objet que je pense copier avec l’opérateur « = ».
Puis, je modifie un élément de liste de l’objet original et enfin j’affiche la valeur de la liste des deux objets.
Et là c’est le drame… Les deux listes ont été modifiées

In [5]: %run exemple.py
affichage liste originale :  [10, 20, 100, 40]
affichage liste modifiée :  [10, 20, 100, 40]

Pourquoi ?

C’est simple, on a copié l’adresse des objets et non le contenu de l’objet, donc quand on modifie les données de l’un, on modifie également l’autre…

Mais, contrairement à la liste, on ne peut pas un truc dans ce genre-là obj2 = obj1(:)…
Non, on ne peut pas, c’est de l’informatique pas de la magie…

Je vous mets donc une solution dans la section qui suit.

Deepcopy

Deepcopy est une fonction qui permet de copier explicitement un objet et son contenu dans un autre objet.

L’exemple précédent se modifie donc de la manière suivante :

from copy import deepcopy


class DataConso(object):
    def __init__(self):
        self.l_conso = []


if __name__ == '__main__':
    maconso = DataConso()
    maconso.l_conso.append(10)
    maconso.l_conso.append(20)
    maconso.l_conso.append(30)
    maconso.l_conso.append(40)

    maconso_copie = deepcopy(maconso)
    maconso_copie.l_conso[2] = 100

    print('affichage liste originale : ', maconso.l_conso)
    print('affichage liste modifiée : ', maconso_copie.l_conso)

Il y a seulement 2 modifications dans ce code :

  • l’import de la fonction deepcopy
  • l’usage de la fonction deepcopy() pour explicitement copier dans l’objet

Ce qui donne donc le résultat souhaité :

In [21]: %run exemple_deepcopy.py
affichage liste originale :  [10, 20, 30, 40]
affichage liste modifiée :  [10, 20, 100, 40]

Bon, voilà, vous n’aurez plus d’excuses pour mélanger les données d’objets ou de listes!

Pour aller plus loin

Les sources des exemples