%load_ext ipythontutorComplément - niveau basique¶
Deux types de copie¶
Pour résumer les deux grands types de copie que l’on a vus dans la vidéo :
La shallow copy - de l’anglais shallow qui signifie superficiel ;
La deep copy - de deep qui signifie profond.
Le module copy¶
Pour réaliser une copie, la méthode la plus simple, en ceci qu’elle fonctionne avec tous les types de manière identique, consiste à utiliser le module standard copy, et notamment :
copy.copypour une copie superficielle ;copy.deepcopypour une copie en profondeur.
import copy
#help(copy.copy)
#help(copy.deepcopy)Un exemple¶
Nous allons voir le résultat des deux formes de copie sur un même sujet de départ.
La copie superficielle / shallow copie / copy.copy¶
N’oubliez pas de cliquer le bouton Next dans la fenêtre pythontutor :
%%ipythontutor height=410 curInstr=6
import copy
# On se donne un objet de départ
source = [
[1, 2, 3], # une liste
{1, 2, 3}, # un ensemble
(1, 2, 3), # un tuple
'123', # un string
123, # un entier
]
# une copie simple renvoie ceci
shallow_copy = copy.copy(source)Vous remarquez que :
la source et la copie partagent tous leurs (sous-)éléments, et notamment la liste
source[0]et l’ensemblesource[1];ainsi, après cette copie, on peut modifier l’un de ces deux objets (la liste ou l’ensemble), et ainsi modifier la source et la copie.
On rappelle aussi que, la source étant une liste, on aurait pu aussi bien faire la copie superficielle avec
shallow2 = source[:]La copie profonde / deep copie / copy.deepcopy¶
Sur le même objet de départ, voici ce que fait la copie profonde :
%%ipythontutor height=410 curInstr=6
import copy
# On se donne un objet de départ
source = [
[1, 2, 3], # une liste
{1, 2, 3}, # un ensemble
(1, 2, 3), # un tuple
'123', # un string
123, # un entier
]
# une copie profonde renvoie ceci
deep_copy = copy.deepcopy(source)Ici, il faut remarquer que :
les deux objets mutables accessibles via
source, c’est-à-dire la listesource[0]et l’ensemblesource[1], ont été tous deux dupliqués ;le tuple correspondant à
source[2]n’est pas dupliqué, mais comme il n’est pas mutable on ne peut pas modifier la copie au travers de la source ;de manière générale, on a la bonne propriété que la source et sa copie ne partagent rien qui soit modifiable ;
et donc on ne peut pas modifier l’un au travers de l’autre.
On retrouve donc à nouveau l’optimisation qui est mise en place dans python pour implémenter les types immuables comme des singletons lorsque c’est possible. Cela a été vu en détail dans le complément consacré à l’opérateur is.
Complément - niveau intermédiaire¶
# on répète car le code précédent a seulement été exposé à pythontutor
import copy
source = [
[1, 2, 3], # une liste
{1, 2, 3}, # un ensemble
(1, 2, 3), # un tuple
'123', # un string
123, # un entier
]
shallow_copy = copy.copy(source)
deep_copy = copy.deepcopy(source)Objets égaux au sens logique¶
Bien sûr ces trois objets se ressemblent si on fait une comparaison logique avec == :
print('source == shallow_copy:', source == shallow_copy)
print('source == deep_copy:', source == deep_copy)source == shallow_copy: True
source == deep_copy: True
Inspectons les objets de premier niveau¶
Mais par contre si on compare l’identité des objets de premier niveau, on voit que source et shallow_copy partagent leurs objets :
# voir la cellule ci-dessous si ceci vous parait peu clair
for i, (source_item, copy_item) in enumerate(zip(source, shallow_copy)):
compare = source_item is copy_item
print(f"source[{i}] is shallow_copy[{i}] -> {compare}")source[0] is shallow_copy[0] -> True
source[1] is shallow_copy[1] -> True
source[2] is shallow_copy[2] -> True
source[3] is shallow_copy[3] -> True
source[4] is shallow_copy[4] -> True
# rappel au sujet de zip et enumerate
# la cellule ci-dessous est essentiellement équivalente à
for i in range(len(source)):
compare = source[i] is shallow_copy[i]
print(f"source[{i}] is shallow_copy[{i}] -> {compare}")The history saving thread hit an unexpected error (OperationalError('no such table: history')).History will not be written to the database.
source[0] is shallow_copy[0] -> True
source[1] is shallow_copy[1] -> True
source[2] is shallow_copy[2] -> True
source[3] is shallow_copy[3] -> True
source[4] is shallow_copy[4] -> True
Alors que naturellement ce n’est pas le cas avec la copie en profondeur :
for i, (source_item, deep_item) in enumerate(zip(source, deep_copy)):
compare = source_item is deep_item
print(f"source[{i}] is deep_copy[{i}] -> {compare}")source[0] is deep_copy[0] -> False
source[1] is deep_copy[1] -> False
source[2] is deep_copy[2] -> True
source[3] is deep_copy[3] -> True
source[4] is deep_copy[4] -> True
On retrouve ici ce qu’on avait déjà remarqué sous pythontutor, à savoir que les trois derniers objets - immuables - n’ont pas été dupliqués comme on aurait pu s’y attendre.
On modifie la source¶
Il doit être clair à présent que, précisément parce que deep_copy est une copie en profondeur, on peut modifier source sans impacter du tout deep_copy.
S’agissant de shallow_copy, par contre, seuls les éléments de premier niveau ont été copiés. Aussi si on fait une modification par exemple à l’intérieur de la liste qui est le premier fils de source, cela sera répercuté dans shallow_copy :
print("avant, source ", source)
print("avant, shallow_copy", shallow_copy)
source[0].append(4)
print("après, source ", source)
print("après, shallow_copy", shallow_copy)avant, source [[1, 2, 3], {1, 2, 3}, (1, 2, 3), '123', 123]
avant, shallow_copy [[1, 2, 3], {1, 2, 3}, (1, 2, 3), '123', 123]
après, source [[1, 2, 3, 4], {1, 2, 3}, (1, 2, 3), '123', 123]
après, shallow_copy [[1, 2, 3, 4], {1, 2, 3}, (1, 2, 3), '123', 123]
Si par contre on remplace complètement un élément de premier niveau dans la source, cela ne sera pas répercuté dans la copie superficielle :
print("avant, source ", source)
print("avant, shallow_copy", shallow_copy)
source[0] = 'remplacement'
print("après, source ", source)
print("après, shallow_copy", shallow_copy)avant, source [[1, 2, 3, 4], {1, 2, 3}, (1, 2, 3), '123', 123]
avant, shallow_copy [[1, 2, 3, 4], {1, 2, 3}, (1, 2, 3), '123', 123]
après, source ['remplacement', {1, 2, 3}, (1, 2, 3), '123', 123]
après, shallow_copy [[1, 2, 3, 4], {1, 2, 3}, (1, 2, 3), '123', 123]
Copie et circularité¶
Le module copy est capable de copier - même en profondeur - des objets contenant des références circulaires.
l = [None]
l[0] = l
l[[...]]copy.copy(l)[[[...]]]copy.deepcopy(l)[[...]]Pour en savoir plus¶
On peut se reporter à la section sur le module copy dans la documentation Python.