Salinan dalam dari dict dengan python

341

Saya ingin membuat salinan dictdalam python. Sayangnya .deepcopy()metode ini tidak ada untuk dict. Bagaimana aku melakukan itu?

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

Baris terakhir seharusnya 3.

Saya ingin modifikasi my_dictitu tidak memengaruhi snapshot my_copy.

Bagaimana aku melakukan itu? Solusinya harus kompatibel dengan Python 3.x.

Olivier Grégoire
sumber
3
Saya tidak tahu apakah ini duplikat, tetapi ini: stackoverflow.com/questions/838642/python-dictionary-deepcopy sangat dekat.
charleslparker

Jawaban:

473

Bagaimana tentang:

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

Python 2 atau 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
sumber
16
Memang itu bekerja untuk contoh sederhana yang saya berikan. Kunci saya bukan angka tetapi benda. Jika saya membaca dokumentasi modul copy, saya harus mendeklarasikan metode __copy __ () / __ deepcopy __ () untuk kunci-kunci tersebut. Terima kasih banyak telah membimbing saya ke sana!
Olivier Grégoire
3
Apakah ada perbedaan dalam kode Python 3.2 dan 2.7? Mereka tampak identik denganku. Jika demikian, akan lebih baik satu blok kode dan pernyataan "Berfungsi untuk Python 3 dan 2"
MestreLion
30
Perlu juga disebutkan copy.deepcopybukan thread aman. Belajar ini dengan cara yang sulit. Di sisi lain, tergantung pada kasus penggunaan Anda, json.loads(json.dumps(d)) adalah benang aman, dan bekerja dengan baik.
merampok
1
@rob Anda harus memposting komentar itu sebagai jawaban. Ini adalah alternatif yang layak. Nuansa keselamatan benang merupakan perbedaan penting.
BuvinJ
3
@ BuvinJ Masalahnya adalah itu json.loadstidak menyelesaikan masalah untuk semua kasus penggunaan di mana dictatribut python tidak JSON serializable. Ini mungkin membantu mereka yang hanya berurusan dengan struktur data sederhana, dari API misalnya, tapi saya rasa itu tidak cukup solusi untuk sepenuhnya menjawab pertanyaan OP.
merampok
36

dict.copy () adalah fungsi salin dangkal untuk
id kamus adalah fungsi bawaan yang memberi Anda alamat variabel

Pertama, Anda perlu memahami "mengapa masalah khusus ini terjadi?"

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

Alamat daftar yang ada di kedua dicts untuk kunci 'a' menunjuk ke lokasi yang sama.
Karenanya ketika Anda mengubah nilai daftar di my_dict, daftar di my_copy juga berubah.


Solusi untuk struktur data yang disebutkan dalam pertanyaan:

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

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

Atau Anda dapat menggunakan deepcopy seperti yang disebutkan di atas.

theBuzzyCoder
sumber
4
Solusi Anda tidak berfungsi untuk kamus bersarang. deepcopy lebih disukai karena alasan itu.
Charles Plager
2
@CharlesPlager Setuju! Tetapi Anda juga harus memperhatikan bahwa mengiris daftar tidak berfungsi pada dict value[:]. Solusinya adalah untuk struktur data tertentu yang disebutkan dalam pertanyaan daripada solusi universal.
theBuzzyCoder
17

Python 3.x

dari copy impor impor

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

Tanpa deepcopy, saya tidak dapat menghapus kamus hostname dari dalam kamus domain saya.

Tanpa deepcopy saya mendapatkan kesalahan berikut:

"RuntimeError: dictionary changed size during iteration"

... ketika saya mencoba untuk menghapus elemen yang diinginkan dari kamus saya di dalam kamus lain.

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

domain adalah objek kamus

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

Output contoh: [asli] domain = {'localdomain': {'localhost': {'all': '4000'}}}}

[baru] domain = {'localdomain': {}}}

Jadi apa yang terjadi di sini adalah saya mengulangi salinan kamus daripada beralih ke kamus itu sendiri. Dengan metode ini, Anda dapat menghapus elemen yang diperlukan.

xpros
sumber
-3

Saya suka dan belajar banyak dari Lasse V. Karlsen. Saya memodifikasinya menjadi contoh berikut, yang menyoroti perbedaan antara salinan kamus dangkal dan salinan dalam:

    import copy

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

Sekarang jika Anda berubah

    my_dict['a'][2] = 7

dan lakukan

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

Anda mendapatkan

    >> my_copy a[2]:  7 ,whereas my_deepcopy a[2]:  3
Rafael Monteiro
sumber
1
Menurut Anda mengapa jawaban ini berbeda dari jawaban Lasse V. Karlsen ? Apa yang ditambahkan bahwa jawaban lain tidak mengatakan?
Olivier Grégoire
Hai, Olivier! Saya tidak mencoba mengambil manfaat dari jawaban Lasse V. Karlsen - dia pada dasarnya memecahkan masalah yang saya miliki, dan saya berhutang kepadanya. Komentar saya tidak berbeda, hanya komplementer. Untuk alasan sederhana yang kontras "salin" dengan "deepcopy". Ini adalah sumber masalah saya, karena saya salah ketika menggunakannya dengan cara yang setara. Bersulang.
Rafael Monteiro
-9

Solusi yang lebih sederhana (dalam pandangan saya) adalah membuat kamus baru dan memperbaruinya dengan isi yang lama:

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

Masalah dengan pendekatan ini adalah mungkin tidak cukup 'dalam'. yaitu tidak mendalam secara rekursif. cukup bagus untuk objek sederhana tetapi tidak untuk kamus bersarang. Berikut adalah contoh di mana itu mungkin tidak cukup dalam:

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

Dengan menggunakan Deepcopy () saya dapat menghilangkan perilaku semi-dangkal, tapi saya pikir orang harus memutuskan pendekatan mana yang tepat untuk aplikasi Anda. Dalam kebanyakan kasus Anda mungkin tidak peduli, tetapi harus menyadari kemungkinan jebakan ... contoh terakhir:

import copy

my_copy2 = copy.deepcopy( my_dict3 )

my_dict1['b']='99'

my_copy2
Out[46]: {'b': {'b': 'z'}, 'c': {'c': 3}}
Eric Hoffman
sumber
12
Ini membuat salinan yang dangkal dari dikt itu, yang bukan apa yang diminta si penanya. Objek yang dikandungnya tidak disalin sendiri. Dan cara penyalinan dangkal yang lebih mudah adalah my_dict.copy()!
Blckknght