Come copiare un dizionario e modificare solo la copia


856

Qualcuno può spiegarmelo? Questo non ha alcun senso per me.

Copio un dizionario in un altro e modifico il secondo ed entrambi vengono modificati. Perché sta succedendo?

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

4
PythonTutor è ottimo per visualizzare i riferimenti Python. Ecco questo codice nell'ultimo passaggio . Puoi vedere dict1e dict2indicare lo stesso dict.
wjandrea,

Risposte:


883

Python non copia mai implicitamente oggetti. Quando imposti dict2 = dict1, li stai facendo fare riferimento allo stesso oggetto dict esatto, quindi quando lo muti, tutti i riferimenti ad esso continuano a fare riferimento all'oggetto nel suo stato corrente.

Se vuoi copiare il dict (che è raro), devi farlo esplicitamente con

dict2 = dict(dict1)

o

dict2 = dict1.copy()

26
Potrebbe essere meglio dire "dict2 e dict1 indicano lo stesso dizionario", non si sta cambiando dict1 o dict2 ma ciò a cui indicano.
GrayWizardx

276
Si noti inoltre che dict.copy () è superficiale, se è presente un elenco nidificato / ecc., Le modifiche verranno applicate ad entrambi. IIRC. Deepcopy lo eviterà.
Will,

16
Non è del tutto corretto che Python non copi mai implicitamente oggetti. Anche i tipi di dati primitivi, come int, float e bool, sono trattati come oggetti (basta fare uno dir(1)per vederlo), ma vengono implicitamente copiati.
Daniel Kullmann,

17
@danielkullmann, penso che potresti avere fraintendimenti su Python in base a come funzionano le altre lingue con cui hai avuto a che fare. In Python, a) Non esiste un concetto di "tipi di dati primitivi". int, floate le boolistanze sono oggetti Python reali e b) gli oggetti di questi tipi non vengono copiati implicitamente quando li si passa, non a livello semantico di Python e nemmeno come dettaglio di implementazione in CPython.
Mike Graham,

39
La retorica priva di fondamento come "Deep copy è considerata dannosa" non è di aiuto. A parità di altre condizioni, la copia superficiale di una complessa struttura di dati ha una probabilità significativamente maggiore di produrre inaspettati problemi relativi al caso limite rispetto alla copia in profondità della stessa struttura. Una copia in cui le modifiche modificano l'oggetto originale non è una copia; è un bug. Ergo, la maggior parte dei casi d'uso dovrebbe chiamare copy.deepcopy()piuttosto che dict()o dict.copy(). La risposta concisa di Imran è dalla parte della sanità mentale, a differenza di questa risposta.
Cecil Curry il

647

Quando assegni dict2 = dict1, non ne stai facendo una copia dict1, si traduce in dict2essere solo un altro nome per dict1.

Per copiare i tipi mutabili come dizionari, utilizzare copy/ deepcopydel copymodulo.

import copy

dict2 = copy.deepcopy(dict1)

80
Per qualsiasi dizionario con cui io abbia mai lavorato, deepcopy è ciò di cui ho bisogno ... Ho perso diverse ore a causa di un bug perché non stavo ottenendo una copia completa di un dizionario nidificato e le mie modifiche alle voci nidificate influivano sull'originale .
flutefreak7,

7
Anch'io. deepcopy () fa il trucco. Stavo incasinando i miei dadi annidati all'interno di una cache rotante aggiungendo un timestamp a una "copia" dell'evento originale. Grazie!
fxstein,

8
Questo in realtà dovrebbe essere contrassegnato come la risposta corretta; Questa risposta è generale e funziona anche per un dizionario di dizionari.
orezvani,

30
Questa dovrebbe essere la risposta accettata. La retorica "sostanziale è considerata dannosa" non comprovata incorporata nella sezione commenti dell'attuale risposta accettata invita palesemente i guai di sincronizzazione quando si copiano dizionari nidificati (come quelli documentati qui) e dovrebbe essere contestata come tale.
Cecil Curry,

deepcopy è la strada da percorrere in caso di una struttura di dizionario complessa. dict1.copy () copia semplicemente i valori delle chiavi come riferimenti e non come oggetti.
Rohith N

182

Mentre dict.copy()e dict(dict1)genera una copia, sono solo poco profonde copie. Se si desidera una copia profonda , copy.deepcopy(dict1)è necessario. Un esempio:

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

Per quanto riguarda le copie superficiali e profonde, dai documenti del modulo Pythoncopy :

La differenza tra copia superficiale e profonda è rilevante solo per gli oggetti composti (oggetti che contengono altri oggetti, come elenchi o istanze di classe):

  • Una copia superficiale costruisce un nuovo oggetto composto e quindi (per quanto possibile) inserisce riferimenti in esso agli oggetti trovati nell'originale.
  • Una copia profonda crea un nuovo oggetto composto e quindi, ricorsivamente, inserisce copie degli oggetti trovati nell'originale.

2
questa dovrebbe essere la risposta giusta in quanto non deve fare esplicitamente il ciclo sul dict e può essere utilizzata per altre strutture primarie.
Nikkolasg,

27
Giusto per chiarire: w=copy.deepcopy(x)è la linea chiave.
alcoholiday

Qual è la differenza tra dict2 = dict1e dict2 = copy.deepcopy(dict1)?
TheTank

1
@TheTank, y = x fa in modo che i due nomi (riferimenti) facciano riferimento a uno stesso oggetto, ovvero "y is x" è True. Qualsiasi modifica apportata sull'oggetto tramite x equivale a una stessa modifica tramite y. Tuttavia u, v, w sono riferimenti a nuovi oggetti diversi che hanno valori copiati da x durante l'istanza. Per quanto riguarda le differenze tra u, v (copia superficiale) e w (deepcopy), consultare docs.python.org/2/library/copy.html
gpanda,

63

Su python 3.5+ c'è un modo più semplice per ottenere una copia superficiale usando l'operatore ** di decompressione. Definito da 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?!'}

** decomprime il dizionario in un nuovo dizionario che viene quindi assegnato a dict2.

Possiamo anche confermare che ogni dizionario ha un ID distinto.

>>>id(dict1)
 178192816

>>>id(dict2)
 178192600

Se è necessaria una copia profonda, allora copy.deepcopy () è ancora la strada da percorrere.


3
Assomiglia terribilmente a puntatori in C ++. Bello per portare a termine il compito, ma per quanto riguarda la leggibilità, tendo a non apprezzare questo tipo di operatori.
Ernesto,

1
Ha una specie di aspetto eccentrico ... ma quando si uniscono più dizionari, la sintassi sembra piuttosto regolare.
PabTorre,

2
Fai attenzione, esegue solo una copia superficiale.
Sebastian Dressler,

hai ragione @SebastianDressler, farò aggiustamenti. thnx.
PabTorre,

2
Utile se vuoi crearne una copia con alcune spezie:dict2 = {**dict1, 'key3':'value3'}
evg656e

48

I modi migliori e più semplici per creare una copia di un dict in Python 2.7 e 3 sono ...

Per creare una copia del dizionario semplice (a livello singolo):

1. Utilizzando il metodo dict () , invece di generare un riferimento che punta al dict esistente.

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. Utilizzo del metodo incorporato update () del dizionario 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'}

Per creare una copia del dizionario nidificato o complesso:

Utilizzare il modulo di copia integrato, che fornisce operazioni generiche di copia superficiale e profonda. Questo modulo è presente in Python 2.7 e 3. *

import copy

my_dict2 = copy.deepcopy(my_dict1)

6
Credo che dict()crei una copia superficiale non una copia profonda. Ciò significa che se hai un nidificato, dictl'esterno dictsarà una copia ma il dict interno sarà un riferimento al dict interno originale.
shmuels,

@shmuels sì, entrambi questi metodi creeranno una copia superficiale, non profonda. Vedi, la risposta aggiornata.
AKay Nirala,

37

Puoi anche creare un nuovo dizionario con una comprensione del dizionario. Questo evita di importare copia.

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

Ovviamente in python> = 2.7 puoi fare:

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

Ma per la retrocompatibilità, il metodo migliore è migliore.


4
Ciò è particolarmente utile se si desidera un maggiore controllo su come e cosa viene esattamente copiato. +1
Avvicinamento

14
Si noti che questo metodo non esegue una copia profonda e se si desidera una copia superficiale senza la necessità di controllare le chiavi da copiare, d2 = dict.copy(d1)non è necessaria alcuna importazione.
Jarek Piórkowski il

1
@ JarekPiórkowski: oppure puoi chiamare un metodo come un metodo:d2 = d1.copy()
Azat Ibrakov

Nota che non hai bisogno della comprensione nel primo esempio. dict.itemsrestituisce già una coppia chiave / valore iterabile. Quindi puoi semplicemente usare dict(mydict.items())(puoi anche solo usare dict(mydict)). Può essere utile comprendere se si desidera filtrare le voci.
Paul Rooney,

22

Oltre alle altre soluzioni fornite, è possibile utilizzare **per integrare il dizionario in un dizionario vuoto, ad es.

shallow_copy_of_other_dict = {**other_dict}.

Ora avrai una copia "superficiale" di other_dict.

Applicato al tuo esempio:

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

Puntatore: differenza tra copie superficiali e profonde


1
Ciò si traduce in una copia superficiale, non in una copia profonda.
sytech,

1
Ci stavo provando ma ho avuto problemi. Funziona solo con Python 3.5 e versioni successive. python.org/dev/peps/pep-0448
ThatGuyRob

19

Le istruzioni di assegnazione in Python non copiano gli oggetti, ma creano associazioni tra una destinazione e un oggetto.

quindi, dict2 = dict1risulta un altro legame tra dict2e l'oggetto a cui si fa dict1riferimento.

se vuoi copiare un dict, puoi usare il copy module. Il modulo di copia ha due interfacce:

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

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

La differenza tra copia superficiale e profonda è rilevante solo per gli oggetti composti (oggetti che contengono altri oggetti, come elenchi o istanze di classe):

Una copia superficiale costruisce un nuovo oggetto composto e quindi (per quanto possibile) inserisce riferimenti in esso agli oggetti trovati nell'originale.

Una copia profonda costruisce un nuovo oggetto composto e quindi, ricorsivamente, inserisce copie degli oggetti trovati nell'originale.

Ad esempio, in 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')

e il risultato è:

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

Puoi copiare e modificare la copia appena costruita in una volta sola chiamando il dictcostruttore con argomenti di parole chiave aggiuntivi:

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

9

Anche questo inizialmente mi confuse, perché provenivo da uno sfondo C.

In C, una variabile è una posizione in memoria con un tipo definito. L'assegnazione a una variabile copia i dati nella posizione di memoria della variabile.

Ma in Python, le variabili agiscono più come puntatori agli oggetti. Quindi assegnare una variabile a un'altra non ne fa una copia, ma fa semplicemente sì che quel nome di variabile punti allo stesso oggetto.


5
le variabili python si comportano più come riferimenti c ++
Ruggero Turra

7
Perché tutto in Python è un oggetto! diveintopython.net/getting_to_know_python/… (sì, questa risposta è in ritardo di molti anni, ma forse è di qualche utilità per qualcuno!)
grimman

1
Credo che la semantica del linguaggio Python affermi che non ci sono "variabili". Sono chiamati "riferimenti nominati"; significa che il riferimento a un oggetto è una stringa sintattica nel codice. Un oggetto può avere molti riferimenti nominati ad esso. Gli oggetti immutabili come ints, float e istanze str ne hanno solo un'istanza per processo. Un int di 1 in memoria non cambia in 2 o in qualche altro valore nello stesso indirizzo di memoria quando si esegue questa operazione myvalue = 1 myvalue = 2
DevPlayer

7

Ogni variabile in Python (cose come dict1o stro __builtins__sono un puntatore a qualche "oggetto" platonico nascosto all'interno della macchina.

Se si imposta dict1 = dict2, si punta semplicemente dict1allo stesso oggetto (o posizione di memoria o qualsiasi analogia che si desidera) di dict2. Ora, l'oggetto a cui fa riferimento dict1è lo stesso oggetto a cui fa riferimento dict2.

Puoi controllare: dict1 is dict2dovrebbe essere True. Inoltre, id(dict1)dovrebbe essere uguale a id(dict2).

Tu vuoi dict1 = copy(dict2), o dict1 = deepcopy(dict2).

La differenza tra copye deepcopy? deepcopyfarà in modo che anche gli elementi di dict2(l'hai indicato in un elenco?) siano copie.

Non uso deepcopymolto - di solito è una cattiva pratica scrivere codice che ne abbia bisogno (secondo me).


Mi sono appena reso conto che devo sempre utilizzare deepcopy in modo che quando copio un dizionario nidificato e comincio a modificare le voci nidificate, gli effetti si verificano solo sulla copia e non sull'originale.
flutefreak7,

6

dict1è un simbolo che fa riferimento a un oggetto dizionario sottostante. Assegnazione dict1di dict2limita ad assegnare lo stesso riferimento. La modifica del valore di una chiave tramite il dict2simbolo modifica l'oggetto sottostante, che influisce anche dict1. Questo è confusionario.

È molto più facile ragionare su valori immutabili rispetto ai riferimenti, quindi copia ogni volta che è possibile:

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

Questo è sintatticamente lo stesso di:

one_year_later = dict(person, age=26)

5

dict2 = dict1non copia il dizionario. Ti dà semplicemente al programmatore un secondo modo ( dict2) per fare riferimento allo stesso dizionario.


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

Ci sono molti modi per copiare l'oggetto Dict, che uso semplicemente

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

12
dict_2 = dict_1.copy()è molto più efficiente e logico.
Jean-François Fabre

2
Nota che se hai un dict all'interno di dict1, con dict_1.copy () le modifiche che fai sul dict interno in dict_2 vengono applicate anche al dict interno in dict_1. In questo caso dovresti usare invece copy.deepcopy (dict_1).
fila

1

Come altri hanno spiegato, il built-in dictnon fa quello che vuoi. Ma in Python2 (e probabilmente anche in 3) puoi facilmente creare una ValueDictclasse con cui copiare, =così puoi essere sicuro che l'originale non cambierà.

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] = ..."

Si prega di fare riferimento al modello di modifica del valore discusso qui: Python 2.7 - sintassi pulita per la modifica del valore . L'osservazione chiave è che stre intsi comportano come i valori in Python (anche se sono in realtà oggetti immutabili sotto il cofano). Mentre lo osservi, ti preghiamo di osservare che nulla è magicamente speciale su stro int. dictpuò essere usato più o meno allo stesso modo, e posso pensare a molti casi in cui ValueDictha senso.


0

il seguente codice, che si trova su dicts che segue la sintassi json più di 3 volte più veloce di 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

mi sono imbattuto in un comportamento peculiare nel tentativo di copiare in profondità la proprietà del dizionario della classe senza assegnarla alla variabile

new = copy.deepcopy(my_class.a)non funziona, ovvero modificando le newmodifichemy_class.a

ma se lo fai old = my_class.ae quindi new = copy.deepcopy(old)funziona perfettamente, ovvero la modifica newnon ha alcun effettomy_class.a

Non sono sicuro del perché questo accada, ma spero che aiuti a risparmiare qualche ora! :)


Quindi, come si fa a fare una copia di fondo my_class.a?
Anthony,

Non è il modo migliore. La buona risposta è muggita.
David Beauchemin,

-1

perché, dict2 = dict1, dict2 contiene il riferimento a dict1. Sia dict1 che dict2 indicano la stessa posizione nella memoria. Questo è solo un caso normale mentre si lavora con oggetti mutabili in Python. Quando lavori con oggetti mutabili in Python devi fare attenzione perché è difficile eseguire il debug. Come il seguente esempio.

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

Questa intenzione di esempio è quella di ottenere tutti gli ID utente inclusi gli ID bloccati. Che abbiamo ottenuto dalla variabile ids ma abbiamo anche aggiornato involontariamente il valore di my_users . quando hai esteso gli id con block_ids my_users è stato aggiornato perché gli id si riferiscono a my_users .


-1

Copia utilizzando un ciclo 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
Funziona solo con dizionari semplici. Perché non usare deepcopy, che è stato costruito appositamente per questo scopo?
Anthony,

Non è il modo migliore. La buona risposta è muggita.
David Beauchemin,

-6

Puoi usare direttamente:

dict2 = eval(repr(dict1))

dove object dict2 è una copia indipendente di dict1, quindi è possibile modificare dict2 senza influire su dict1.

Funziona con qualsiasi tipo di oggetto.


4
Questa risposta non è corretta e non deve essere utilizzata. Una classe definita dall'utente, ad esempio, potrebbe non avere un appropriato __repr__per essere ricostruita da eval, né la classe dell'oggetto può essere nell'ambito corrente da chiamare. Anche se si attacca ai tipi predefiniti, ciò non riuscirà se lo stesso oggetto viene archiviato in più chiavi, poiché dict2avrebbero quindi due oggetti separati. Un dizionario autoreferenziale, dove dict1contiene se stesso, conterrà invece Ellipsis. Sarebbe meglio usaredict1.copy()
Eldritch Cheese

Non si prevede che gli oggetti (o "valori") abbiano sempre una rappresentazione fedele mediante stringhe di caratteri, non in un modo normalmente leggibile dall'uomo.
Alexey,
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.