Copie complète d'un dict en python

342

Je voudrais faire une copie complète d'un dictfichier en python. Malheureusement, la .deepcopy()méthode n'existe pas pour le dict. Comment je fais ça?

>>> my_dict = {'a': [1, 2, 3], 'b': [4, 5, 6]}
>>> my_copy = my_dict.deepcopy()
Traceback (most recent calll last):
  File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'deepcopy'
>>> my_copy = my_dict.copy()
>>> my_dict['a'][2] = 7
>>> my_copy['a'][2]
7

La dernière ligne devrait être 3.

Je voudrais que les modifications apportées my_dictn'affectent pas l'instantané my_copy.

Comment je fais ça? La solution doit être compatible avec Python 3.x.

Olivier Grégoire
la source
3
Je ne sais pas si c'est un doublon, mais ceci: stackoverflow.com/questions/838642/python-dictionary-deepcopy est terriblement proche.
charleslparker

Réponses:

474

Que diriez-vous:

import copy
d = { ... }
d2 = copy.deepcopy(d)

Python 2 ou 3:

Python 3.2 (r32:88445, Feb 20 2011, 21:30:00) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import copy
>>> my_dict = {'a': [1, 2, 3], 'b': [4, 5, 6]}
>>> my_copy = copy.deepcopy(my_dict)
>>> my_dict['a'][2] = 7
>>> my_copy['a'][2]
3
>>>
Lasse V. Karlsen
la source
16
En effet, cela fonctionne pour l'exemple simplifié que j'ai donné. Mes clés ne sont pas des nombres mais des objets. Si je lis la documentation du module de copie, je dois déclarer une méthode __copy __ () / __ deepcopy __ () pour les clés. Merci beaucoup de m'y avoir conduit!
Olivier Grégoire
3
Y a-t-il une différence entre les codes Python 3.2 et 2.7? Ils me semblent identiques. Si c'est le cas, il vaudrait mieux un seul bloc de code et une déclaration "Works for Python 3 and 2"
MestreLion
30
Il convient également de mentionner que le copy.deepcopythread n'est pas sûr. J'ai appris cela à la dure. En revanche, en fonction de votre cas d'utilisation, json.loads(json.dumps(d)) est thread-safe et fonctionne bien.
voler
1
@rob vous devez poster ce commentaire comme réponse. C'est une alternative viable. La nuance de sécurité du fil est une distinction importante.
BuvinJ
3
@BuvinJ Le problème est que json.loadscela ne résout pas le problème pour tous les cas d'utilisation où les dictattributs python ne sont pas sérialisables JSON. Cela peut aider ceux qui ne traitent que de structures de données simples, à partir d'une API par exemple, mais je ne pense pas que ce soit une solution suffisante pour répondre pleinement à la question du PO.
voler
36

dict.copy () est une fonction de copie superficielle pour le dictionnaire
id est une fonction intégrée qui vous donne l'adresse de la variable

Vous devez d'abord comprendre "pourquoi ce problème se produit-il?"

In [1]: my_dict = {'a': [1, 2, 3], 'b': [4, 5, 6]}

In [2]: my_copy = my_dict.copy()

In [3]: id(my_dict)
Out[3]: 140190444167808

In [4]: id(my_copy)
Out[4]: 140190444170328

In [5]: id(my_copy['a'])
Out[5]: 140190444024104

In [6]: id(my_dict['a'])
Out[6]: 140190444024104

L'adresse de la liste présente dans les deux dict pour la clé «a» pointe vers le même emplacement.
Par conséquent, lorsque vous modifiez la valeur de la liste dans my_dict, la liste dans my_copy change également.


Solution pour la structure de données mentionnée dans la question:

In [7]: my_copy = {key: value[:] for key, value in my_dict.items()}

In [8]: id(my_copy['a'])
Out[8]: 140190444024176

Ou vous pouvez utiliser la copie profonde comme mentionné ci-dessus.

theBuzzyCoder
la source
4
Votre solution ne fonctionne pas pour les dictionnaires imbriqués. la copie profonde est préférable pour cette raison.
Charles Plager
2
@CharlesPlager est d'accord! Mais vous devez également noter que le découpage de liste ne fonctionne pas sur dict value[:]. La solution a été pour la structure de données particulière mentionnée dans la question plutôt que pour une solution universelle.
theBuzzyCoder
17

Python 3.x

à partir de l'importation de copie en profondeur

my_dict = {'one': 1, 'two': 2}
new_dict_deepcopy = deepcopy(my_dict)

Sans deepcopy, je ne peux pas supprimer le dictionnaire de noms d'hôte de mon dictionnaire de domaine.

Sans deepcopy, j'obtiens l'erreur suivante:

"RuntimeError: dictionary changed size during iteration"

... lorsque j'essaie de supprimer l'élément souhaité de mon dictionnaire à l'intérieur d'un autre dictionnaire.

import socket
import xml.etree.ElementTree as ET
from copy import deepcopy

domaine est un objet dictionnaire

def remove_hostname(domain, hostname):
    domain_copy = deepcopy(domain)
    for domains, hosts in domain_copy.items():
        for host, port in hosts.items():
           if host == hostname:
                del domain[domains][host]
    return domain

Exemple de sortie: [orginal] domain = {'localdomain': {'localhost': {'all': '4000'}}}

[nouveau] domaines = {'domaine local': {}}}

Donc, ce qui se passe ici, c'est que j'itère sur une copie d'un dictionnaire plutôt que sur le dictionnaire lui-même. Avec cette méthode, vous pouvez supprimer des éléments selon vos besoins.

xpros
la source
-3

J'aime et j'ai beaucoup appris de Lasse V. Karlsen. Je l'ai modifié dans l'exemple suivant, qui souligne assez bien la différence entre les copies de dictionnaire peu profondes et les copies profondes:

    import copy

    my_dict = {'a': [1, 2, 3], 'b': [4, 5, 6]}
    my_copy = copy.copy(my_dict)
    my_deepcopy = copy.deepcopy(my_dict)

Maintenant, si vous changez

    my_dict['a'][2] = 7

et fait

    print("my_copy a[2]: ",my_copy['a'][2],",whereas my_deepcopy a[2]: ", my_deepcopy['a'][2])

vous obtenez

    >> my_copy a[2]:  7 ,whereas my_deepcopy a[2]:  3
Rafael Monteiro
la source
1
Pourquoi pensez-vous que cette réponse est différente de la réponse de Lasse V. Karlsen ? Qu'est-ce que cela ajoute que l'autre réponse ne dit pas?
Olivier Grégoire
Salut Olivier! Je n'essaie pas de prendre le mérite de la réponse de Lasse V. Karlsen - il a essentiellement résolu le problème que j'avais, et je lui suis redevable. Mon commentaire n'est pas différent, il est juste complémentaire. Pour la simple raison qu'il oppose "copie" à "copie profonde". Ce fut la source de mon problème, car je me trompais en les utilisant de manière équivalente. À votre santé.
Rafael Monteiro
-9

Une solution plus simple (à mon avis) consiste à créer un nouveau dictionnaire et à le mettre à jour avec le contenu de l'ancien:

my_dict={'a':1}

my_copy = {}

my_copy.update( my_dict )

my_dict['a']=2

my_dict['a']
Out[34]: 2

my_copy['a']
Out[35]: 1

Le problème avec cette approche est qu'elle n'est peut-être pas «assez profonde». c'est-à-dire n'est pas récursivement profond. assez bon pour les objets simples mais pas pour les dictionnaires imbriqués. Voici un exemple où il peut ne pas être assez profond:

my_dict1={'b':2}

my_dict2={'c':3}

my_dict3={ 'b': my_dict1, 'c':my_dict2 }

my_copy = {}

my_copy.update( my_dict3 )

my_dict1['b']='z'

my_copy
Out[42]: {'b': {'b': 'z'}, 'c': {'c': 3}}

En utilisant Deepcopy (), je peux éliminer le comportement semi-superficiel, mais je pense qu'il faut décider quelle approche convient à votre application. Dans la plupart des cas, vous ne vous en souciez peut-être pas, mais vous devez être conscient des pièges possibles ... dernier exemple:

import copy

my_copy2 = copy.deepcopy( my_dict3 )

my_dict1['b']='99'

my_copy2
Out[46]: {'b': {'b': 'z'}, 'c': {'c': 3}}
Eric Hoffman
la source
12
Cela fait une copie superficielle du dict, ce qui n'est pas ce que le questionneur demandait. Les objets qu'il contient ne sont pas copiés eux-mêmes. Et un moyen plus simple de copie superficielle est my_dict.copy()!
Blckknght