Zrozumienie dict.copy () - płytkie czy głębokie?


429

Podczas czytania dokumentacji dict.copy()napisano, że tworzy ona płytką kopię słownika. To samo dotyczy książki, którą obserwuję (Beazley's Python Reference), która mówi:

Metoda m.copy () tworzy płytką kopię elementów zawartych w obiekcie odwzorowującym i umieszcza je w nowym obiekcie odwzorowującym.

Rozważ to:

>>> original = dict(a=1, b=2)
>>> new = original.copy()
>>> new.update({'c': 3})
>>> original
{'a': 1, 'b': 2}
>>> new
{'a': 1, 'c': 3, 'b': 2}

Więc założyłem, że to zaktualizuje wartość original(i doda „c”: 3) również, ponieważ robiłem płytką kopię. Na przykład, jeśli robisz to dla listy:

>>> original = [1, 2, 3]
>>> new = original
>>> new.append(4)
>>> new, original
([1, 2, 3, 4], [1, 2, 3, 4])

Działa to zgodnie z oczekiwaniami.

Skoro oba są płytkimi kopiami, dlaczego dict.copy()nie działa tak, jak się spodziewam? Czy moje rozumienie kopiowania płytkiego vs. głębokiego jest błędne?


2
Dziwne, że nie tłumaczą „płytki”. Wiedza poufna, mrugnięcie. Tylko dict i klucze są kopią, podczas gdy zagnieżdżone dykty wewnątrz tego pierwszego poziomu są odnośnikami, na przykład nie można ich usunąć w pętli. Zatem dict.copy () Pythona w tym przypadku nie jest ani użyteczne, ani intuicyjne. Dziękuję za twoje pytanie.
gseattle

Odpowiedzi:


990

Przez „płytkie kopiowanie” oznacza to, że zawartość słownika nie jest kopiowana według wartości, a jedynie tworzenie nowego odwołania.

>>> a = {1: [1,2,3]}
>>> b = a.copy()
>>> a, b
({1: [1, 2, 3]}, {1: [1, 2, 3]})
>>> a[1].append(4)
>>> a, b
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})

Natomiast głęboka kopia skopiuje całą zawartość według wartości.

>>> import copy
>>> c = copy.deepcopy(a)
>>> a, c
({1: [1, 2, 3, 4]}, {1: [1, 2, 3, 4]})
>>> a[1].append(5)
>>> a, c
({1: [1, 2, 3, 4, 5]}, {1: [1, 2, 3, 4]})

Więc:

  1. b = a: Przypisanie odniesienia, marka ai bwskazuje ten sam obiekt.

    Ilustracja „a = b”: „a” i „b” oba wskazują na „{1: L}”, „L” wskazuje na „[1, 2, 3]”.

  2. b = a.copy(): Płytkie kopiowanie ai bstaną się dwoma odizolowanymi obiektami, ale ich zawartość nadal będzie mieć to samo odwołanie

    Ilustracja „b = a.copy ()”: „a” wskazuje na „{1: L}”, „b” wskazuje na „{1: M}”, „L” i „M” oba wskazują na „[ 1, 2, 3] ”.

  3. b = copy.deepcopy(a): Głębokie kopiowanie, aa bstruktura i treść zostają całkowicie odizolowane.

    Ilustracja „b = copy.deepcopy (a)”: „a” wskazuje na „{1: L}”, „L” wskazuje na „[1, 2, 3]”;  „b” wskazuje na „{1: M}”, „M” wskazuje na inną instancję „[1, 2, 3]”.


Dobra odpowiedź, ale możesz rozważyć poprawienie błędu gramatycznego w pierwszym zdaniu. I nie ma powodu, aby nie używać Lponownie b. Takie postępowanie uprościłoby przykład.
Tom Russell

@kennytm: Jaka jest właściwie różnica między pierwszymi dwoma przykładami? Otrzymujesz ten sam wynik, ale nieco inną wewnętrzną implementację, ale na co to ma znaczenie?
JavaSa

@TomRussell: Lub ktokolwiek, ponieważ to pytanie jest dość stare, moje pytanie wyjaśniające jest przeznaczone dla wszystkich
JavaSa

@JavaSa To ważne, powiedzmy, że tak b[1][0] = 5. Jeśli bjest to płytka kopia, właśnie się zmieniłeś a[1][0].
Tom Russell

2
Świetne wytłumaczenie ... naprawdę uratowało mi dzień! Dzięki ... Czy to samo można zastosować do listy, str i innych typów danych Pythona?
Bhuro

38

Nie jest to kwestia głębokiej lub płytkiej kopii, żadne z tych działań nie jest głęboką kopią.

Tutaj:

>>> new = original 

tworzysz nowe odwołanie do listy / słownika, do którego odwołuje się oryginał.

podczas gdy tutaj:

>>> new = original.copy()
>>> # or
>>> new = list(original) # dict(original)

tworzysz nową listę / dict, która jest wypełniona kopią referencji obiektów zawartych w oryginalnym pojemniku.


31

Weź ten przykład:

original = dict(a=1, b=2, c=dict(d=4, e=5))
new = original.copy()

Teraz zmieńmy wartość na „płytkim” (pierwszym) poziomie:

new['a'] = 10
# new = {'a': 10, 'b': 2, 'c': {'d': 4, 'e': 5}}
# original = {'a': 1, 'b': 2, 'c': {'d': 4, 'e': 5}}
# no change in original, since ['a'] is an immutable integer

Teraz zmieńmy wartość o jeden poziom głębiej:

new['c']['d'] = 40
# new = {'a': 10, 'b': 2, 'c': {'d': 40, 'e': 5}}
# original = {'a': 1, 'b': 2, 'c': {'d': 40, 'e': 5}}
# new['c'] points to the same original['d'] mutable dictionary, so it will be changed

8
no change in original, since ['a'] is an immutable integerTo. W rzeczywistości odpowiada na zadane pytanie.
CivFan

8

Dodanie do odpowiedzi KennyTM. Gdy wykonujesz płytką kopię parent.copy (), tworzony jest nowy słownik z tymi samymi kluczami, ale wartości nie są kopiowane, do których się odwołują. Jeśli dodasz nową wartość do parent_copy , nie wpłynie to na parent, ponieważ copy_copy jest nowym słownikiem brak odniesienia.

parent = {1: [1,2,3]}
parent_copy = parent.copy()
parent_reference = parent

print id(parent),id(parent_copy),id(parent_reference)
#140690938288400 140690938290536 140690938288400

print id(parent[1]),id(parent_copy[1]),id(parent_reference[1])
#140690938137128 140690938137128 140690938137128

parent_copy[1].append(4)
parent_copy[2] = ['new']

print parent, parent_copy, parent_reference
#{1: [1, 2, 3, 4]} {1: [1, 2, 3, 4], 2: ['new']} {1: [1, 2, 3, 4]}

Hash (ID) wartości macierzystego [1] , parent_copy [1], są takie same co oznacza, [1,2,3], o dominującej [1] i parent_copy [1] przechowywano w identyfikatorze 140690938288400.

Ale skróty typu parent i parent_copy są różne, co oznacza, że ​​są to różne słowniki, a parent_copy to nowy słownik o wartościach odniesienia do wartości typu parent


5

„nowe” i „oryginalny” są różne dicts, dlatego można aktualizować tylko jeden z nich .. Te przedmioty są płytkie, kopiowane, a nie sam dict.


2

Treści są płytko kopiowane.

Więc jeśli oryginał dictzawiera jeden listlub inny dictionary, modyfikowanie jednego z nich w oryginale lub jego płytkiej kopii spowoduje modyfikację ich (tego listlub dictdrugiego) w drugim.


1

W drugiej części powinieneś użyć new = original.copy()

.copyi =są różne rzeczy.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.