Copia profonda di un dict in pitone


341

Vorrei fare una copia profonda di un dictin Python. Sfortunatamente il .deepcopy()metodo non esiste per dict. Come lo faccio?

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

L'ultima riga dovrebbe essere 3.

Vorrei che le modifiche my_dictnon influissero sull'istantanea my_copy.

Come lo faccio? La soluzione dovrebbe essere compatibile con Python 3.x.


3
Non so se sia un duplicato, ma questo: stackoverflow.com/questions/838642/python-dictionary-deepcopy è terribilmente vicino.
charleslparker,

Risposte:


473

Che ne dite di:

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

Python 2 o 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
>>>

16
In effetti, questo funziona per l'esempio semplificato che ho dato. Le mie chiavi non sono numeri ma oggetti. Se leggo la documentazione del modulo di copia, devo dichiarare un metodo __copy __ () / __ deepcopy __ () per le chiavi. Grazie mille per avermi portato lì!
Olivier Grégoire,

3
C'è qualche differenza nei codici Python 3.2 e 2.7? Mi sembrano identici. Se è così, sarebbe meglio un singolo blocco di codice e una frase "Funziona sia per Python 3 che per 2"
MestreLion

30
Vale anche la pena ricordare che copy.deepcopynon è thread-safe. L'ho imparato nel modo più duro. D'altra parte, a seconda del caso d'uso, json.loads(json.dumps(d)) è thread-safe e funziona bene.
derubare il

1
@rob dovresti pubblicare quel commento come risposta. È un'alternativa praticabile. La sfumatura di sicurezza del filo è una distinzione importante.
BuvinJ,

3
@BuvinJ Il problema è che json.loadsnon risolve il problema per tutti i casi d'uso in cui gli dictattributi python non sono serializzabili JSON. Può aiutare coloro che si occupano solo di semplici strutture dati, ad esempio da un'API, ma non credo che sia sufficiente una soluzione per rispondere pienamente alla domanda del PO.
rapina il

36

dict.copy () è una funzione di copia superficiale per
id dizionario è una funzione integrata che ti dà l'indirizzo della variabile

Per prima cosa devi capire "perché sta accadendo questo particolare problema?"

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'indirizzo dell'elenco presente in entrambi i caratteri per il tasto 'a' indica la stessa posizione.
Pertanto, quando si modifica il valore dell'elenco in my_dict, cambia anche l'elenco in my_copy.


Soluzione per la struttura dei dati menzionata nella domanda:

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

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

Oppure puoi usare deepcopy come menzionato sopra.


4
La tua soluzione non funziona per i dizionari nidificati. per questo motivo è preferibile la deepcopy.
Charles Plager,

2
@CharlesPlager concordato! Ma dovresti anche notare che la suddivisione in liste non funziona su dict value[:]. La soluzione era per la particolare struttura di dati menzionata nella domanda piuttosto che una soluzione universale.
theBuzzyCoder il

17

Python 3.x

da copia importare deepcopy

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

Senza deepcopy, non riesco a rimuovere il dizionario dei nomi host dal mio dizionario di dominio.

Senza deepcopy viene visualizzato il seguente errore:

"RuntimeError: dictionary changed size during iteration"

... quando provo a rimuovere l'elemento desiderato dal mio dizionario all'interno di un altro dizionario.

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

domain è un oggetto dizionario

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

Esempio di output: [originale] domains = {'localdomain': {'localhost': {'all': '4000'}}}

[new] domains = {'localdomain': {}}}

Quindi quello che sta succedendo qui è che sto iterando su una copia di un dizionario piuttosto che iterando sul dizionario stesso. Con questo metodo, è possibile rimuovere gli elementi secondo necessità.


-3

Mi piace e ho imparato molto da Lasse V. Karlsen. L'ho modificato nel seguente esempio, che evidenzia piuttosto bene la differenza tra copie di dizionari superficiali e copie di profondità:

    import copy

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

Ora se cambi

    my_dict['a'][2] = 7

e fai

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

hai capito

    >> my_copy a[2]:  7 ,whereas my_deepcopy a[2]:  3

1
Perché pensi che questa risposta sia diversa dalla risposta di Lasse V. Karlsen ? Cosa aggiunge che l'altra risposta non dice?
Olivier Grégoire

Ciao Olivier! Non sto cercando di prendere il merito della risposta di Lasse V. Karlsen - ha sostanzialmente risolto il problema che avevo e sono in debito con lui. Il mio commento non è diverso, è solo complementare. Per la semplice ragione che contrappone "copia" a "deepcopy". Questa era la fonte del mio problema, poiché mi sbagliavo quando li usavo in modo equivalente. Saluti.
Rafael Monteiro,

-9

Una soluzione più semplice (a mio avviso) è quella di creare un nuovo dizionario e aggiornarlo con i contenuti di quello precedente:

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

Il problema con questo approccio è che potrebbe non essere "abbastanza profondo". cioè non è ricorsivamente profondo. abbastanza buono per oggetti semplici ma non per dizionari nidificati. Ecco un esempio in cui potrebbe non essere abbastanza profondo:

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

Usando Deepcopy () posso eliminare il comportamento semi-superficiale, ma penso che si debba decidere quale approccio è giusto per la propria applicazione. Nella maggior parte dei casi potrebbe non interessarti, ma dovresti essere consapevole delle possibili insidie ​​... esempio finale:

import copy

my_copy2 = copy.deepcopy( my_dict3 )

my_dict1['b']='99'

my_copy2
Out[46]: {'b': {'b': 'z'}, 'c': {'c': 3}}

12
Questo fa una copia superficiale del dict, che non è quello che chiedeva l'interrogante. Gli oggetti che contiene non vengono copiati da soli. E un modo più semplice di copia superficiale è my_dict.copy()!
Blckknght,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.