Comment copier un dictionnaire et modifier uniquement la copie


856

Quelqu'un pourrait-il me l'expliquer? Cela n'a aucun sens pour moi.

Je copie un dictionnaire dans un autre et édite le second et les deux sont modifiés. Pourquoi cela arrive-t-il?

>>> dict1 = {"key1": "value1", "key2": "value2"}
>>> dict2 = dict1
>>> dict2
{'key2': 'value2', 'key1': 'value1'}
>>> dict2["key2"] = "WHY?!"
>>> dict1
{'key2': 'WHY?!', 'key1': 'value1'}

4
PythonTutor est idéal pour visualiser les références Python. Voici ce code à la dernière étape . Vous pouvez voir dict1et dict2pointer vers le même dict.
wjandrea

Réponses:


883

Python ne copie jamais implicitement des objets. Lorsque vous définissezdict2 = dict1 , vous les faites se référer au même objet dict exact, donc lorsque vous le mutez, toutes les références à celui-ci continuent de faire référence à l'objet dans son état actuel.

Si vous voulez copier le dict (ce qui est rare), vous devez le faire explicitement avec

dict2 = dict(dict1)

ou

dict2 = dict1.copy()

26
Il vaut peut-être mieux dire "dict2 et dict1 pointent vers le même dictionnaire", vous ne changez pas dict1 ou dict2 mais ce à quoi ils pointent.
GrayWizardx

276
Notez également que le dict.copy () est superficiel, s'il y a une liste imbriquée / etc, des modifications seront appliquées aux deux. IIRC. Deepcopy évitera cela.
Will

16
Il n'est pas tout à fait correct que python ne copie jamais implicitement des objets. Les types de données primitifs, tels que int, float et bool, sont également traités comme des objets (faites juste un dir(1)pour le voir), mais ils sont implicitement copiés.
daniel kullmann

17
@danielkullmann, je pense que vous pourriez avoir des malentendus sur Python en fonction de la façon dont les autres langages que vous avez traités fonctionnent. En Python, a) Il n'y a pas de concept de "types de données primitifs". int, floatet les boolinstances sont de vrais objets Python, et b) les objets de ces types ne sont pas copiés implicitement lorsque vous les passez, pas à un niveau sémantique Python à coup sûr et même pas en tant que détail d'implémentation dans CPython.
Mike Graham

39
Une rhétorique non corroborée comme «la copie profonde est considérée comme nuisible» est inutile. Toutes choses étant égales par ailleurs, la copie superficielle d'une structure de données complexe est beaucoup plus susceptible de générer des problèmes de cas de bord inattendus que la copie profonde de la même structure. Une copie dans laquelle des modifications modifient l'objet d'origine n'est pas une copie; c'est un bug. Ergo, la plupart des cas d'utilisation doivent absolument appeler copy.deepcopy()plutôt que dict()ou dict.copy(). La réponse concise d' Imran est du bon côté de la raison, contrairement à cette réponse.
Cecil Curry

647

Lorsque vous attribuez dict2 = dict1, vous ne faites pas de copie de dict1, cela se traduit par dict2être juste un autre nom pour dict1.

Pour copier les types mutables comme les dictionnaires, utilisez copy/ deepcopydu copymodule.

import copy

dict2 = copy.deepcopy(dict1)

80
Pour tous les dictionnaires avec lesquels je travaille, la copie profonde est ce dont j'ai besoin ... .
flutefreak7

7
Pareil ici. deepcopy () fait l'affaire. J'étais en train de gâcher mes dict imbriqués dans un cache rotatif en ajoutant un horodatage à une «copie» de l'événement d'origine. Je vous remercie!
fxstein

8
En fait, cela devrait être marqué comme la bonne réponse; Cette réponse est générale et fonctionne également pour un dictionnaire de dictionnaires.
orezvani

30
Ce devrait être la réponse acceptée. La rhétorique non corroborée «La copie profonde est considérée comme nuisible» intégrée dans la section des commentaires de la réponse actuellement acceptée invite ouvertement à des problèmes de synchronisation lors de la copie de dictionnaires imbriqués (tels que ceux documentés ici) et doit être contestée en tant que telle.
Cecil Curry

deepcopy est la voie à suivre en cas de structure de dictionnaire complexe. dict1.copy () copie simplement les valeurs des clés en tant que références et non en tant qu'objets.
Rohith N

182

Tandis que dict.copy()et dict(dict1)génère une copie, ce ne sont que des copies superficielles . Si vous voulez une profonde copie, copy.deepcopy(dict1)est nécessaire. Un exemple:

>>> source = {'a': 1, 'b': {'m': 4, 'n': 5, 'o': 6}, 'c': 3}
>>> copy1 = x.copy()
>>> copy2 = dict(x)
>>> import copy
>>> copy3 = copy.deepcopy(x)
>>> source['a'] = 10  # a change to first-level properties won't affect copies
>>> source
{'a': 10, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> copy1
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> copy2
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> copy3
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}
>>> source['b']['m'] = 40  # a change to deep properties WILL affect shallow copies 'b.m' property
>>> source
{'a': 10, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> copy1
{'a': 1, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> copy2
{'a': 1, 'c': 3, 'b': {'m': 40, 'o': 6, 'n': 5}}
>>> copy3  # Deep copy's 'b.m' property is unaffected
{'a': 1, 'c': 3, 'b': {'m': 4, 'o': 6, 'n': 5}}

Concernant les copies superficielles vs profondes, à partir des documents du module Pythoncopy :

La différence entre la copie superficielle et la copie profonde n'est pertinente que pour les objets composés (objets qui contiennent d'autres objets, comme des listes ou des instances de classe):

  • Une copie superficielle construit un nouvel objet composé, puis (dans la mesure du possible) y insère des références aux objets trouvés dans l'original.
  • Une copie profonde construit un nouvel objet composé, puis, récursivement, insère des copies des objets trouvés dans l'original.

2
cela devrait être la bonne réponse car elle ne fait pas de boucle explicite sur le dict et peut être utilisée pour d'autres structures primaires.
Nikkolasg

27
Juste pour clarifier: w=copy.deepcopy(x)c'est la ligne clé.
alcoholiday

Quelle est la difference entre dict2 = dict1et dict2 = copy.deepcopy(dict1)?
TheTank

1
@TheTank, y = x fait que les deux noms (références) se réfèrent à un même objet, c'est-à-dire que "y est x" est vrai. Toute modification apportée à l'objet via x équivaut à une même modification via y. Cependant u, v, w sont des références à de nouveaux objets différents dont les valeurs sont copiées à partir de x lors de l'instanciation. En ce qui concerne les différences entre u, v (copie superficielle) et w (copie profonde), veuillez consulter docs.python.org/2/library/copy.html
gpanda

63

Sur python 3.5+, il existe un moyen plus simple de réaliser une copie superficielle en utilisant l'opérateur de décompression **. Défini par Pep 448 .

>>>dict1 = {"key1": "value1", "key2": "value2"}
>>>dict2 = {**dict1}
>>>print(dict2)
{'key1': 'value1', 'key2': 'value2'}
>>>dict2["key2"] = "WHY?!"
>>>print(dict1)
{'key1': 'value1', 'key2': 'value2'}
>>>print(dict2)
{'key1': 'value1', 'key2': 'WHY?!'}

** décompresse le dictionnaire dans un nouveau dictionnaire qui est ensuite affecté à dict2.

Nous pouvons également confirmer que chaque dictionnaire a un identifiant distinct.

>>>id(dict1)
 178192816

>>>id(dict2)
 178192600

Si une copie complète est nécessaire, alors copy.deepcopy () est toujours le chemin à parcourir.


3
Cela ressemble énormément à des pointeurs en C ++. Bien pour accomplir la tâche, mais en termes de lisibilité, j'ai tendance à détester ce type d'opérateurs.
Ernesto

1
Il a une sorte de look c'ish ... mais lors de la fusion de plusieurs dictionnaires, la syntaxe semble assez fluide.
PabTorre

2
Attention à cela, il n'effectue qu'une copie superficielle.
Sebastian Dressler

vous avez raison @SebastianDressler, je ferai des ajustements. thnx.
PabTorre

2
Utile si vous souhaitez créer une copie avec quelques épices:dict2 = {**dict1, 'key3':'value3'}
evg656e

48

Les meilleurs et les plus simples moyens de créer une copie d'un dict en Python 2.7 et 3 sont ...

Pour créer une copie d'un dictionnaire simple (à un niveau):

1. Utilisation de la méthode dict () , au lieu de générer une référence qui pointe vers le dict existant.

my_dict1 = dict()
my_dict1["message"] = "Hello Python"
print(my_dict1)  # {'message':'Hello Python'}

my_dict2 = dict(my_dict1)
print(my_dict2)  # {'message':'Hello Python'}

# Made changes in my_dict1 
my_dict1["name"] = "Emrit"
print(my_dict1)  # {'message':'Hello Python', 'name' : 'Emrit'}
print(my_dict2)  # {'message':'Hello Python'}

2. Utilisation de la méthode intégrée update () du dictionnaire python.

my_dict2 = dict()
my_dict2.update(my_dict1)
print(my_dict2)  # {'message':'Hello Python'}

# Made changes in my_dict1 
my_dict1["name"] = "Emrit"
print(my_dict1)  # {'message':'Hello Python', 'name' : 'Emrit'}
print(my_dict2)  # {'message':'Hello Python'}

Pour créer une copie d'un dictionnaire imbriqué ou complexe:

Utilisez le module de copie intégré, qui fournit des opérations génériques de copie superficielle et profonde. Ce module est présent à la fois dans Python 2.7 et 3. *

import copy

my_dict2 = copy.deepcopy(my_dict1)

6
Je crois que dict()crée une copie superficielle et non une copie profonde. Cela signifie que si vous avez un imbriqué, dictl'extérieur dictsera une copie, mais le dict intérieur sera une référence au dict intérieur d'origine.
shmuels

@shmuels oui, ces deux méthodes créeront une copie superficielle, pas la copie profonde. Voir, la réponse mise à jour.
AKay Nirala

37

Vous pouvez également créer un nouveau dictionnaire avec une compréhension du dictionnaire. Cela évite d'importer une copie.

dout = dict((k,v) for k,v in mydict.items())

Bien sûr, en python> = 2,7, vous pouvez faire:

dout = {k:v for k,v in mydict.items()}

Mais pour la compatibilité ascendante, la meilleure méthode est meilleure.


4
Ceci est particulièrement utile si vous souhaitez avoir plus de contrôle sur la manière et le contenu exact de la copie. +1
ApproachingDarknessFish

14
Notez que cette méthode n'effectue pas de copie complète, et si vous voulez une copie superficielle sans avoir besoin de contrôler les clés à copier, d2 = dict.copy(d1)ne nécessite pas non plus d'importations.
Jarek Piórkowski

1
@ JarekPiórkowski: ou vous pouvez appeler une méthode comme une méthode:d2 = d1.copy()
Azat Ibrakov

Notez que vous n'avez pas besoin de la compréhension du premier exemple. dict.itemsrenvoie déjà une paire clé / valeur itérable. Vous pouvez donc simplement utiliser dict(mydict.items())(vous pouvez également simplement utiliser dict(mydict)). Il peut être utile d'avoir la compréhension si vous souhaitez filtrer les entrées.
Paul Rooney

22

En plus des autres solutions fournies, vous pouvez utiliser **pour intégrer le dictionnaire dans un dictionnaire vide, par exemple,

shallow_copy_of_other_dict = {**other_dict}.

Vous aurez maintenant une copie "superficielle" de other_dict .

Appliqué à votre exemple:

>>> dict1 = {"key1": "value1", "key2": "value2"}
>>> dict2 = {**dict1}
>>> dict2
{'key1': 'value1', 'key2': 'value2'}
>>> dict2["key2"] = "WHY?!"
>>> dict1
{'key1': 'value1', 'key2': 'value2'}
>>>

Pointeur: différence entre les copies peu profondes et profondes


1
Il en résulte une copie superficielle, pas une copie complète.
sytech

1
J'essayais ça mais j'avais du mal. Cela ne fonctionne que pour python 3.5 et supérieur. python.org/dev/peps/pep-0448
ThatGuyRob

19

Les instructions d'affectation en Python ne copient pas d'objets, elles créent des liaisons entre une cible et un objet.

donc, dict2 = dict1il en résulte une autre liaison entre dict2et l'objet quidict1 réfère.

si vous souhaitez copier un dict, vous pouvez utiliser le copy module. Le module de copie a deux interfaces:

copy.copy(x)
Return a shallow copy of x.

copy.deepcopy(x)
Return a deep copy of x.

La différence entre la copie superficielle et la copie profonde n'est pertinente que pour les objets composés (objets qui contiennent d'autres objets, comme des listes ou des instances de classe):

Une copie superficielle construit un nouvel objet composé, puis (dans la mesure du possible) y insère des références aux objets trouvés dans l'original.

Une copie profonde construit un nouvel objet composé, puis, récursivement, y insère des copies des objets trouvés dans l'original.

Par exemple, en python 2.7.9:

>>> import copy
>>> a = [1,2,3,4,['a', 'b']]
>>> b = a
>>> c = copy.copy(a)
>>> d = copy.deepcopy(a)
>>> a.append(5)
>>> a[4].append('c')

et le résultat est:

>>> a
[1, 2, 3, 4, ['a', 'b', 'c'], 5]
>>> b
[1, 2, 3, 4, ['a', 'b', 'c'], 5]
>>> c
[1, 2, 3, 4, ['a', 'b', 'c']]
>>> d
[1, 2, 3, 4, ['a', 'b']]

10

Vous pouvez copier et modifier la copie nouvellement construite en une seule fois en appelant le dictconstructeur avec des arguments de mots clés supplémentaires:

>>> dict1 = {"key1": "value1", "key2": "value2"}
>>> dict2 = dict(dict1, key2="WHY?!")
>>> dict1
{'key2': 'value2', 'key1': 'value1'}
>>> dict2
{'key2': 'WHY?!', 'key1': 'value1'}

9

Cela m'a aussi dérouté, au départ, car je venais d'un milieu C.

En C, une variable est un emplacement en mémoire avec un type défini. L'affectation à une variable copie les données dans l'emplacement de mémoire de la variable.

Mais en Python, les variables agissent plus comme des pointeurs vers des objets. Donc, assigner une variable à une autre ne fait pas de copie, cela fait simplement pointer le nom de la variable vers le même objet.


5
les variables python agissent plus comme des références c ++
Ruggero Turra

7
Parce que tout en Python est un objet! diveintopython.net/getting_to_know_python/… (oui, cette réponse est en retard de plusieurs années, mais peut-être est-elle utile à quelqu'un!)
grimman

1
Je crois que la sémantique du langage Python dit qu'il n'y a pas de "variables". Ils sont appelés "références nommées"; ce qui signifie que la référence à un objet est une chaîne syntaxique dans le code. Un objet peut avoir plusieurs références nommées. Les objets immuables comme les instances ints et float et str n'ont qu'une seule instance par processus. Un entier de 1 en mémoire ne change pas en 2 ou une autre valeur à la même adresse mémoire lorsque vous effectuez cette opération myvalue = 1 myvalue = 2
DevPlayer

7

Chaque variable en python (des trucs comme dict1ou strou __builtins__est un pointeur vers un "objet" platonique caché à l'intérieur de la machine.

Si vous définissez dict1 = dict2, vous pointez simplement dict1vers le même objet (ou emplacement de mémoire, ou toute analogie que vous aimez) que dict2. Maintenant, l'objet référencé par dict1est le même objet référencé par dict2.

Vous pouvez vérifier: dict1 is dict2devrait l'être True. En outre, id(dict1)devrait être le même que id(dict2).

Vous voulez dict1 = copy(dict2), ou dict1 = deepcopy(dict2).

La différence entre copyet deepcopy? deepcopys'assurera que les éléments de dict2(l'avez-vous pointé sur une liste?) sont également des copies.

Je n'utilise pas deepcopybeaucoup - c'est généralement une mauvaise pratique d'écrire du code qui en a besoin (à mon avis).


Je viens de réaliser que je dois toujours utiliser Deepcopy afin que lorsque je copie un dictionnaire imbriqué et commence à modifier des entrées imbriquées, les effets se produisent uniquement sur la copie et non sur l'original.
flutefreak7

6

dict1est un symbole qui fait référence à un objet dictionnaire sous-jacent. L'affectation dict1à dict2affecte simplement la même référence. La modification de la valeur d'une clé via le dict2symbole modifie l'objet sous-jacent, ce qui affecte également dict1. Ceci est déroutant.

Il est beaucoup plus facile de raisonner sur des valeurs immuables que sur des références, faites donc des copies autant que possible:

person = {'name': 'Mary', 'age': 25}
one_year_later = {**person, 'age': 26}  # does not mutate person dict

Ceci est syntaxiquement le même que:

one_year_later = dict(person, age=26)

5

dict2 = dict1ne copie pas le dictionnaire. Cela vous donne simplement au programmeur une deuxième façon ( dict2) de faire référence au même dictionnaire.


5
>>> dict2 = dict1
# dict2 is bind to the same Dict object which binds to dict1, so if you modify dict2, you will modify the dict1

Il existe de nombreuses façons de copier un objet Dict, j'utilise simplement

dict_1 = {
           'a':1,
           'b':2
         }
dict_2 = {}
dict_2.update(dict_1)

12
dict_2 = dict_1.copy()est beaucoup plus efficace et logique.
Jean-François Fabre

2
Notez que si vous avez un dict dans dict1, avec dict_1.copy (), les modifications que vous effectuez sur le dict interne dans dict_2 sont également appliquées au dict interne dans dict_1. Dans ce cas, vous devez utiliser copy.deepcopy (dict_1) à la place.
queise

1

Comme d'autres l'ont expliqué, la fonction intégrée dictne fait pas ce que vous voulez. Mais en Python2 (et probablement 3 aussi), vous pouvez facilement créer une ValueDictclasse qui copie avec =ainsi vous pouvez être sûr que l'original ne changera pas.

class ValueDict(dict):

    def __ilshift__(self, args):
        result = ValueDict(self)
        if isinstance(args, dict):
            dict.update(result, args)
        else:
            dict.__setitem__(result, *args)
        return result # Pythonic LVALUE modification

    def __irshift__(self, args):
        result = ValueDict(self)
        dict.__delitem__(result, args)
        return result # Pythonic LVALUE modification

    def __setitem__(self, k, v):
        raise AttributeError, \
            "Use \"value_dict<<='%s', ...\" instead of \"d[%s] = ...\"" % (k,k)

    def __delitem__(self, k):
        raise AttributeError, \
            "Use \"value_dict>>='%s'\" instead of \"del d[%s]" % (k,k)

    def update(self, d2):
        raise AttributeError, \
            "Use \"value_dict<<=dict2\" instead of \"value_dict.update(dict2)\""


# test
d = ValueDict()

d <<='apples', 5
d <<='pears', 8
print "d =", d

e = d
e <<='bananas', 1
print "e =", e
print "d =", d

d >>='pears'
print "d =", d
d <<={'blueberries': 2, 'watermelons': 315}
print "d =", d
print "e =", e
print "e['bananas'] =", e['bananas']


# result
d = {'apples': 5, 'pears': 8}
e = {'apples': 5, 'pears': 8, 'bananas': 1}
d = {'apples': 5, 'pears': 8}
d = {'apples': 5}
d = {'watermelons': 315, 'blueberries': 2, 'apples': 5}
e = {'apples': 5, 'pears': 8, 'bananas': 1}
e['bananas'] = 1

# e[0]=3
# would give:
# AttributeError: Use "value_dict<<='0', ..." instead of "d[0] = ..."

Veuillez vous référer au modèle de modification de lvalue discuté ici: Python 2.7 - syntaxe propre pour la modification de lvalue . L'observation clé est que stret intse comportent comme des valeurs en Python (même si ce sont en fait des objets immuables sous le capot). Pendant que vous observez cela, veuillez également noter que rien n'est magiquement spécial à propos de strou int. dictpeut être utilisé de la même manière, et je pense à de nombreux cas où ValueDictcela a du sens.


0

le code suivant, qui est sur dicts et qui suit la syntaxe json plus de 3 fois plus vite que deepcopy

def CopyDict(dSrc):
    try:
        return json.loads(json.dumps(dSrc))
    except Exception as e:
        Logger.warning("Can't copy dict the preferred way:"+str(dSrc))
        return deepcopy(dSrc)

0

j'ai rencontré un comportement particulier en essayant de copier en profondeur la propriété du dictionnaire de la classe sans l'attribuer à une variable

new = copy.deepcopy(my_class.a)ne fonctionne pas, c'est-à-dire que la modification newmodifiemy_class.a

mais si vous le faites old = my_class.aet que new = copy.deepcopy(old)cela fonctionne parfaitement, c'est-à-dire que la modification newn'affecte pasmy_class.a

Je ne sais pas pourquoi cela se produit, mais j'espère que cela vous fera gagner quelques heures! :)


Alors, comment faites-vous une copie profonde de my_class.a?
Anthony

Pas la meilleure façon. Une bonne réponse est ci-dessous.
David Beauchemin

-1

parce que, dict2 = dict1, dict2 contient la référence à dict1. Dict1 et dict2 pointent tous deux vers le même emplacement dans la mémoire. C'est juste un cas normal lorsque vous travaillez avec des objets mutables en python. Lorsque vous travaillez avec des objets modifiables en python, vous devez être prudent car il est difficile à déboguer. Tels que l'exemple suivant.

 my_users = {
        'ids':[1,2],
        'blocked_ids':[5,6,7]
 }
 ids = my_users.get('ids')
 ids.extend(my_users.get('blocked_ids')) #all_ids
 print ids#output:[1, 2, 5, 6, 7]
 print my_users #output:{'blocked_ids': [5, 6, 7], 'ids': [1, 2, 5, 6, 7]}

Cet exemple d'intention est d'obtenir tous les ID utilisateur, y compris les ID bloqués. Que nous avons obtenu de la variable ids, mais nous avons également mis à jour la valeur de my_users par inadvertance . lorsque vous avez étendu les identifiants avec block_ids, my_users a été mis à jour car les identifiants font référence à my_users .


-1

Copie à l'aide d'une boucle for:

orig = {"X2": 674.5, "X3": 245.0}

copy = {}
for key in orig:
    copy[key] = orig[key]

print(orig) # {'X2': 674.5, 'X3': 245.0}
print(copy) # {'X2': 674.5, 'X3': 245.0}
copy["X2"] = 808
print(orig) # {'X2': 674.5, 'X3': 245.0}
print(copy) # {'X2': 808, 'X3': 245.0}

1
Cela ne fonctionne que pour les dictionnaires simples. Pourquoi ne pas utiliser deepcopyce qui est expressément conçu à cet effet?
Anthony

Pas la meilleure façon. Une bonne réponse est ci-dessous.
David Beauchemin

-6

Vous pouvez utiliser directement:

dict2 = eval(repr(dict1))

où l'objet dict2 est une copie indépendante de dict1, vous pouvez donc modifier dict2 sans affecter dict1.

Cela fonctionne pour tout type d'objet.


4
Cette réponse est incorrecte et ne doit pas être utilisée. Une classe définie par l'utilisateur, par exemple, peut ne pas avoir de méthode appropriée __repr__pour être reconstruite par eval, ni la classe de l'objet dans la portée actuelle à appeler. Même en restant avec des types intégrés, cela échouera si le même objet est stocké sous plusieurs clés, comme cela dict2aurait alors deux objets distincts. Un dictionnaire auto-référentiel, où dict1contient lui-même, contiendra à la place Ellipsis. Il serait préférable d'utiliserdict1.copy()
Eldritch Cheese

On ne s'attend pas à ce que les objets (ou "valeurs") aient toujours une représentation fidèle par des chaînes de caractères, pas d'une manière lisible par l'homme habituelle dans tous les cas.
Alexey
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.