Comprendre dict.copy () - peu profond ou profond?

429

En lisant la documentation de dict.copy(), il indique qu'il crée une copie superficielle du dictionnaire. Il en va de même pour le livre que je suis (Beazley's Python Reference), qui dit:

La méthode m.copy () crée une copie superficielle des éléments contenus dans un objet de mappage et les place dans un nouvel objet de mappage.

Considère ceci:

>>> original = dict(a=1, b=2)
>>> new = original.copy()
>>> new.update({'c': 3})
>>> original
{'a': 1, 'b': 2}
>>> new
{'a': 1, 'c': 3, 'b': 2}

J'ai donc supposé que cela mettrait à jour la valeur de original(et ajouterait 'c': 3) également puisque je faisais une copie superficielle. Comme si vous le faites pour une liste:

>>> original = [1, 2, 3]
>>> new = original
>>> new.append(4)
>>> new, original
([1, 2, 3, 4], [1, 2, 3, 4])

Cela fonctionne comme prévu.

Puisque les deux sont des copies superficielles, pourquoi est-ce que dict.copy()cela ne fonctionne pas comme je m'y attendais? Ou ma compréhension de la copie superficielle vs profonde est défectueuse?

user225312
la source
2
Curieux qu'ils n'expliquent pas "superficiel". Connaissance d'initiés, clin d'œil. Juste le dict et les clés sont une copie tandis que les dict imbriqués à l'intérieur de ce premier niveau sont des références, ne peuvent pas être supprimés dans une boucle par exemple. Ainsi, dict.copy () de Python dans ce cas n'est ni utile ni intuitif. Merci pour ta question.
gseattle

Réponses:

991

Par "copie superficielle", cela signifie que le contenu du dictionnaire n'est pas copié par valeur, mais simplement en créant une nouvelle référence.

>>> a = {1: [1,2,3]}
>>> b = a.copy()
>>> a, b
({1: [1, 2, 3]}, {1: [1, 2, 3]})
>>> a[1].append(4)
>>> a, b
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})

En revanche, une copie complète copiera tous les contenus par valeur.

>>> import copy
>>> c = copy.deepcopy(a)
>>> a, c
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})
>>> a[1].append(5)
>>> a, c
({1: [1, 2, 3, 4, 5]}, {1: [1, 2, 3, 4]})

Donc:

  1. b = a: Affectation de référence, marque aet bpointe vers le même objet.

    Illustration de 'a = b': 'a' et 'b' pointent tous les deux vers '{1: L}', 'L' pointe vers '[1, 2, 3]'.

  2. b = a.copy(): Copie superficielle, aet bdeviendra deux objets isolés, mais leur contenu partagera toujours la même référence

    Illustration de 'b = a.copy ()': 'a' pointe vers '{1: L}', 'b' pointe vers '{1: M}', 'L' et 'M' pointe vers '[ 1, 2, 3] ».

  3. b = copy.deepcopy(a): Copie profonde, aet bla structure et le contenu deviennent complètement isolés.

    Illustration de 'b = copy.deepcopy (a)': 'a' pointe vers '{1: L}', 'L' pointe vers '[1, 2, 3]';  'b' pointe vers '{1: M}', 'M' pointe vers une instance différente de '[1, 2, 3]'.

kennytm
la source
Bonne réponse, mais vous pourriez envisager de corriger l'erreur grammaticale dans votre première phrase. Et il n'y a aucune raison de ne pas utiliser de Lnouveau dans b. Cela simplifierait l'exemple.
Tom Russell
@kennytm: Quelle est la différence entre les deux premiers exemples, en fait? Vous obtenez le même résultat, mais une implémentation interne légèrement différente, mais qu'importe?
JavaSa
@TomRussell: Ou n'importe qui, puisque cette question est assez ancienne, ma question de clarification s'adresse à tout le monde
JavaSa
@JavaSa C'est important si, disons, vous le faites b[1][0] = 5. Si bc'est une copie superficielle, vous venez de changer a[1][0].
Tom Russell
2
Excellente explication, ... m'a vraiment sauvé la journée! Merci ... Cette même chose peut-elle être appliquée à la liste, à str et à d'autres types de données de python?
Bhuro
38

Ce n'est pas une question de copie profonde ou de copie superficielle, rien de ce que vous faites n'est une copie profonde.

Ici:

>>> new = original 

vous créez une nouvelle référence à la liste / dict référencée par l'original.

ici:

>>> new = original.copy()
>>> # or
>>> new = list(original) # dict(original)

vous créez une nouvelle liste / dict qui est remplie d'une copie des références des objets contenus dans le conteneur d'origine.

Lie Ryan
la source
31

Prenez cet exemple:

original = dict(a=1, b=2, c=dict(d=4, e=5))
new = original.copy()

Modifions maintenant une valeur au niveau (peu profond) (premier):

new['a'] = 10
# new = {'a': 10, 'b': 2, 'c': {'d': 4, 'e': 5}}
# original = {'a': 1, 'b': 2, 'c': {'d': 4, 'e': 5}}
# no change in original, since ['a'] is an immutable integer

Modifions maintenant une valeur d'un niveau plus en profondeur:

new['c']['d'] = 40
# new = {'a': 10, 'b': 2, 'c': {'d': 40, 'e': 5}}
# original = {'a': 1, 'b': 2, 'c': {'d': 40, 'e': 5}}
# new['c'] points to the same original['d'] mutable dictionary, so it will be changed
eumiro
la source
8
no change in original, since ['a'] is an immutable integerCette. Il répond en fait à la question posée.
CivFan
8

Ajout à la réponse de kennytm. Lorsque vous effectuez une copie superficielle parent.copy (), un nouveau dictionnaire est créé avec les mêmes clés, mais les valeurs ne sont pas copiées, elles sont référencées.Si vous ajoutez une nouvelle valeur à parent_copy, cela n'affectera pas parent car parent_copy est un nouveau dictionnaire pas de référence.

parent = {1: [1,2,3]}
parent_copy = parent.copy()
parent_reference = parent

print id(parent),id(parent_copy),id(parent_reference)
#140690938288400 140690938290536 140690938288400

print id(parent[1]),id(parent_copy[1]),id(parent_reference[1])
#140690938137128 140690938137128 140690938137128

parent_copy[1].append(4)
parent_copy[2] = ['new']

print parent, parent_copy, parent_reference
#{1: [1, 2, 3, 4]} {1: [1, 2, 3, 4], 2: ['new']} {1: [1, 2, 3, 4]}

La valeur de hachage (id) du parent [1] , parent_copy [1] sont identiques, ce qui implique [1,2,3] du parent [1] et parent_copy [1] stockés à l'ID 140690938288400.

Mais le hachage de parent et parent_copy sont différents, ce qui implique que ce sont des dictionnaires différents et parent_copy est un nouveau dictionnaire ayant des valeurs faisant référence aux valeurs de parent

Vkreddy Komatireddy
la source
5

"nouveau" et "original" sont des dict différents, c'est pourquoi vous pouvez mettre à jour un seul d'entre eux .. Les éléments sont copiés en profondeur, pas le dict lui-même.

Joril
la source
2

Le contenu est copié peu profond.

Donc, si l'original dictcontient un listou un autre dictionary, en modifier un dans l'original ou sa copie superficielle les modifiera (le listou le dict) dans l'autre.

Chasseur de la jungle
la source
1

Dans votre deuxième partie, vous devez utiliser new = original.copy()

.copyet =sont des choses différentes.

朱骏 杰
la source