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.

Image non disponible

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

Image non disponible

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]]
 

 

_images/repr_shallow.png

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]])

_images/repr_deep.png

Exemple avec dictionnaire
import 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]}