Głęboka kopia słownika w języku python

341

Chciałbym zrobić głęboką kopię dictPythona. Niestety .deepcopy()metoda nie istnieje dla dict. Jak mogę to zrobić?

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

Ostatnia linia powinna być 3.

Chciałbym, aby te modyfikacje my_dictnie miały wpływu na migawkę my_copy.

Jak mogę to zrobić? Rozwiązanie powinno być kompatybilne z Python 3.x.

Olivier Grégoire
źródło
3
Nie wiem, czy to duplikat, ale to: stackoverflow.com/questions/838642/python-dictionary-deepcopy jest strasznie blisko.
charleslparker

Odpowiedzi:

473

Co powiesz na:

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

Python 2 lub 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
źródło
16
Rzeczywiście, działa to na uproszczony przykład, który podałem. Moje klucze to nie cyfry, ale przedmioty. Jeśli czytam dokumentację modułu kopiowania, muszę zadeklarować metodę __copy __ () / __ deepcopy __ () dla kluczy. Dziękuję bardzo za doprowadzenie mnie tam!
Olivier Grégoire
3
Czy jest jakaś różnica w kodach Python 3.2 i 2.7? Wydają mi się identyczne. Jeśli tak, lepszy byłby pojedynczy blok kodu i instrukcja „Działa zarówno dla Pythona 3, jak i 2”
MestreLion
30
Warto również wspomnieć, że copy.deepcopynie jest bezpieczny dla wątków. Nauczyłem się tego na własnej skórze. Z drugiej strony, w zależności od przypadku użycia, json.loads(json.dumps(d)) jest bezpieczny dla wątków i działa dobrze.
okradać
1
@rob powinieneś opublikować ten komentarz jako odpowiedź. To realna alternatywa. Niuans dotyczący bezpieczeństwa nici jest ważnym rozróżnieniem.
BuvinJ
3
@BuvinJ Problem polega na tym, json.loadsże nie rozwiązuje on problemu we wszystkich przypadkach użycia, w których dictatrybuty Pythona nie są serializowane przez JSON. Może to pomóc tym, którzy mają do czynienia tylko z prostymi strukturami danych, na przykład z API, ale nie sądzę, że to wystarczające rozwiązanie, aby w pełni odpowiedzieć na pytanie OP.
okr.
36

dict.copy () to płytka funkcja kopiowania dla słownika
id jest wbudowaną funkcją, która podaje adres zmiennej

Najpierw musisz zrozumieć „dlaczego tak się dzieje?”

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

Adres listy występującej w obu nagraniach dla klucza „a” wskazuje tę samą lokalizację.
Dlatego po zmianie wartości listy w my_dict zmienia się również lista w my_copy.


Rozwiązanie dla struktury danych wspomniane w pytaniu:

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

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

Lub możesz użyć deepcopy, jak wspomniano powyżej.

theBuzzyCoder
źródło
4
Twoje rozwiązanie nie działa w przypadku zagnieżdżonych słowników. z tego powodu preferowana jest głęboka kopia.
Charles Plager
2
@CharlesPlager Zgoda! Ale powinieneś również zauważyć, że segmentowanie list nie działa w przypadku dyktowania value[:]. Rozwiązaniem była konkretna struktura danych wspomniana w pytaniu, a nie rozwiązanie uniwersalne.
theBuzzyCoder
17

Python 3.x

z kopiowania importu kopii

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

Bez szczegółowej analizy nie mogę usunąć słownika nazw hostów ze słownika domen.

Bez głębokiej kopii pojawia się następujący błąd:

"RuntimeError: dictionary changed size during iteration"

... kiedy próbuję usunąć pożądany element z mojego słownika w innym słowniku.

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

domena jest obiektem słownika

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

Przykładowe dane wyjściowe: [orginal] domains = {'localdomain': {'localhost': {'all': '4000'}}}

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

To, co się tutaj dzieje, to iteracja nad kopią słownika, a nie iteracja nad samym słownikiem. Dzięki tej metodzie możesz usuwać elementy w razie potrzeby.

xpros
źródło
-3

Bardzo lubię i dużo się nauczyłem od Lasse V. Karlsen. Zmodyfikowałem go w następujący przykład, który dość dobrze podkreśla różnicę między płytkimi kopiami słownika a kopiami głębokimi:

    import copy

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

Teraz jeśli się zmienisz

    my_dict['a'][2] = 7

i robić

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

dostajesz

    >> my_copy a[2]:  7 ,whereas my_deepcopy a[2]:  3
Rafael Monteiro
źródło
1
Jak myślisz, dlaczego ta odpowiedź jest inna niż odpowiedź Lassego V. Karlsena ? Co to dodaje, że druga odpowiedź nie mówi?
Olivier Grégoire
Cześć, Olivier! Nie staram się czerpać zasługi z odpowiedzi Lassego V. Karlsena - zasadniczo rozwiązał mój problem i mam wobec niego dług. Mój komentarz nie jest inny, jest tylko komplementarny. Z tego prostego powodu, że kontrastuje „kopiowanie” z „głębokim kopiowaniem”. To było źródłem mojego problemu, ponieważ myliłem się, gdy używałem ich w równoważny sposób. Twoje zdrowie.
Rafael Monteiro
-9

Prostszym (moim zdaniem) rozwiązaniem jest utworzenie nowego słownika i zaktualizowanie go o zawartość starego:

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

Problem z tym podejściem polega na tym, że może nie być on „wystarczająco głęboki”. tzn. nie jest rekurencyjnie głęboki. wystarczająco dobre dla prostych obiektów, ale nie dla zagnieżdżonych słowników. Oto przykład, w którym może nie być wystarczająco głęboki:

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

Korzystając z Deepcopy (), mogę wyeliminować częściowo płytkie zachowanie, ale myślę, że należy zdecydować, które podejście jest odpowiednie dla twojej aplikacji. W większości przypadków możesz się tym nie przejmować, ale powinieneś zdawać sobie sprawę z możliwych pułapek ... ostatni przykład:

import copy

my_copy2 = copy.deepcopy( my_dict3 )

my_dict1['b']='99'

my_copy2
Out[46]: {'b': {'b': 'z'}, 'c': {'c': 3}}
Eric Hoffman
źródło
12
To tworzy płytką kopię dyktanda, o co nie pytał pytający. Obiekty w nim zawarte nie są kopiowane same. A łatwiejszym sposobem płytkiego kopiowania jest my_dict.copy()!
Blckknght