CPGE Oujda Spé
Gestion mémoire :Copies superficielles et
profondes
Comment sont gérés en mémoire les différents composants Python : entiers flottants, chaines de caractères, listes, …
n Ne pas oublier que dans Python tout est objet
n Par exemple quand on passe des paramètres à une fonction, ce ne sont pas des valeurs que l'on transmet mais des objets représentant ces valeurs
n Attention : le processus d'affection est particulier en Python et à ne pas confondre avec un appel de méthode.
n Les variables sont des références vers des objets
n Autrement dit : une variable est un identifiant pointant vers une référence d'objet.
Il faut prendre garde à
la façon dont Python copie une liste. Illustrons cela sur un exemple.
·
Copie d'une liste :
jusqu'ici tout va bien.
>>> liste = ['a', 'b', 'c'] # définition d'une liste
>>> copie = liste #
puis copie de la liste
>>> print copie
['a', 'b', 'c']
·
Modification d'un élément
de la liste originelle. Tout se passe comme prévu.
>>> liste[0] = 'modifié' # Modifions un élément
dans liste
>>> print liste
['modifié', 'b', 'c']
·
Mais, ô surprise, la
copie aussi a été modifiée… contrairement à ce qu'on aurait pu attendre.
>>> print copie #
regardons ce qu'est devenue la copie
['modifié', 'b', 'c']
Explication : en
fait, une liste en Python ne contient que l'adresse mémoire où sont stockés ses
éléments ; c'est ce que l'on appelle un pointeur.

Quand on copie liste dans copie,
c'est cette adresse mémoire qui est copiée. C'est un alias qui
est créé.

Quand on modifie un
élément d'une liste, il est alors aussi modifié dans la copie. L'avantage étant
qu'on encombre moins la mémoire centrale puisque les éléments de la liste ne
figurent qu'en un seul emplacement mémoire.
Pour preuve, on peut
utiliser la fonction intégrée id() qui
retourne un entier long étant l'image de l'adresse mémoire où est stocké
l'objet passé en argument. Les deux objets ont le même
« identifiant ».
>>> print id(liste), id(copie)
139807842040576 139807842040576
On peut contourner ce
problème grâce à copie =
liste[:] qui fait une « copie
superficielle » :
>>> liste = ['a', 'b', 'c'] # définition d'une liste
>>> copie = liste[:] #
puis copie superficielle de la liste
>>> print copie
['a', 'b', 'c']
>>> liste[0] = 'modifié' # Modifions un élément
dans liste
>>> print liste
['modifié', 'b', 'c']
>>> print copie #
regardons ce qu'est devenue la copie
['a', 'b', 'c']
Python fait une copie
des éléments de la liste, mais c'est l'adresse mémoire des objets qui est
copiée. Aussi, si l'un des éléments de la liste est aussi une liste, on
retombe sur le même problème :
>>> liste = ['a', 'b', [0, 1]] # définition d'une liste
>>> copie = liste[:] #
puis copie superficielle de la liste
>>> liste[0] = 'modifié' #
Modifions un élément dans liste
>>> liste[2][0] = 'MODIF' # et un élément dans
liste[2]
>>> print liste; print copie #
regardons ce qu'est devenue la copie
['modifié', 'b', ['MODIF',1]]
['a', 'b', ['MODIF',1]]
Autre
Exemple
import copy
a = [[1],[2],[3]] # Création d'une liste
b = copy.copy(a) # Copier l'objet liste
print "Avant la copie", "=>"
print "a=> ", a
print "b=> ",b
# Modifier l'originale
a[0][0] = 0
a[1] = None
print "Après la copie", "=>"
print "a=> ", a
print "b=> ",b
"""Avant la copie =>a=> [[1], [2], [3]]b=> [[1], [2], [3]]Après la copie =>a=> [[0], None, [3]]b=> [[0], [2], [3]]Avec des listes imbriquées :
>>> a = [[1, 2], [3]]
>>> b = copy(a)
>>> b[0][0] = 0
>>> (a, b)
([[0, 2], [3]],
[[0, 2], [3]])
Pour contourner
totalement le problème, utilisez la méthode liste.deepcopy() du
module standard copy qui effectue une « copie
profonde »:
>>> from copy import deepcopy
>>> copie = deepcopy(liste)
Copie profonde (deep copy)
>>> import copy
>>> a = [[1, 2], [3]]
>>> b = copy.deepcopy(a)
>>> b[0][0] = 0
>>> (a, b)
([[1, 2], [3]], [[0,
2], [3]])
Exemple avec dictionnaireimport copy
# Définition d'un dictionnaire
params = {'Police': 12, 'Style': 'Gras', 'Couleur': [255, 0, 255]}
# Afficher le dictionnaire
print 'Originale: ',params
# créer une copie non superficielle de params
params2 = copy.deepcopy(params)
# Affiher la copie du dictionnaire
print 'Copie: ',params2
# Supprimer une valeur dans la copie
# Note: la suppression n'a aucun impact
# sur la copie originale contrairement
# à la copie superficielle
params2['Couleur'].remove(0)
print 'Originale: ',params
print 'Copie: ', params2
########### Résultat ##############
# Originale: {'Police': 12, 'Style': 'Gras', 'Couleur': [255, 0, 255]}
# Copie: {'Police': 12, 'Style': 'Gras', 'Couleur': [255, 0, 255]}
# Originale: {'Police': 12, 'Style': 'Gras', 'Couleur': [255, 0, 255]}
# Copie: {'Police': 12, 'Style': 'Gras', 'Couleur': [255, 255]}