Dict.copy () verstehen - flach oder tief?

428

Beim Lesen der Dokumentation für dict.copy()heißt es, dass eine flache Kopie des Wörterbuchs erstellt wird. Gleiches gilt für das Buch, dem ich folge (Beazleys Python-Referenz), in dem es heißt:

Die Methode m.copy () erstellt eine flache Kopie der in einem Zuordnungsobjekt enthaltenen Elemente und platziert sie in einem neuen Zuordnungsobjekt.

Bedenken Sie:

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

Daher nahm ich an, dass dies den Wert von original(und 'c': 3 hinzufügen) aktualisieren würde, auch da ich eine flache Kopie machte. Zum Beispiel, wenn Sie es für eine Liste tun:

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

Dies funktioniert wie erwartet.

Da beide flache Kopien sind, warum dict.copy()funktioniert das nicht so, wie ich es erwartet habe? Oder ist mein Verständnis von flachem und tiefem Kopieren fehlerhaft?

user225312
quelle
2
Urig, dass sie nicht "flach" erklären. Insiderwissen, zwinker. Nur das Diktat und die Schlüssel sind eine Kopie, während verschachtelte Diktate in dieser ersten Ebene Referenzen sind und beispielsweise nicht in einer Schleife gelöscht werden können. Daher ist Pythons dict.copy () in diesem Fall weder nützlich noch intuitiv. Danke für deine Frage.
Gseattle

Antworten:

988

Unter "flachem Kopieren" versteht man, dass der Inhalt des Wörterbuchs nicht nach Wert kopiert wird, sondern lediglich eine neue Referenz erstellt wird.

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

Im Gegensatz dazu kopiert eine tiefe Kopie alle Inhalte nach Wert.

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

Damit:

  1. b = a: Referenzzuweisung, Make aund bzeigt auf dasselbe Objekt.

    Abbildung von 'a = b': 'a' und 'b' zeigen beide auf '{1: L}', 'L' zeigt auf '[1, 2, 3]'.

  2. b = a.copy(): Flaches Kopieren aund bwird zu zwei isolierten Objekten, deren Inhalt jedoch immer noch dieselbe Referenz hat

    Abbildung von 'b = a.copy ()': 'a' zeigt auf '{1: L}', 'b' zeigt auf '{1: M}', 'L' und 'M' zeigen beide auf '[ 1, 2, 3] '.

  3. b = copy.deepcopy(a): Tiefes Kopieren aund bStruktur und Inhalt von werden vollständig isoliert.

    Abbildung von 'b = copy.deepcopy (a)': 'a' zeigt auf '{1: L}', 'L' zeigt auf '[1, 2, 3]';  'b' zeigt auf '{1: M}', 'M' zeigt auf eine andere Instanz von '[1, 2, 3]'.

kennytm
quelle
Gute Antwort, aber Sie könnten in Betracht ziehen, den Grammatikfehler in Ihrem ersten Satz zu korrigieren. Und es gibt keinen Grund, nicht Lwieder in zu verwenden b. Dies würde das Beispiel vereinfachen.
Tom Russell
@kennytm: Was ist eigentlich der Unterschied zwischen den ersten beiden Beispielen? Sie erhalten dort das gleiche Ergebnis, aber eine etwas andere innere Implementierung, aber worauf kommt es an?
JavaSa
@ TomRussell: Oder irgendjemand, da diese Frage ziemlich alt ist, ist meine Klärungsfrage für alle
JavaSa
@ JavaSa Es ist wichtig, ob Sie es tun b[1][0] = 5. Wenn bes sich um eine flache Kopie handelt, haben Sie sie gerade geändert a[1][0].
Tom Russell
2
Tolle Erklärung, ... hat mir wirklich den Tag gerettet! Danke ... Kann dies auch auf die Liste, str und andere Datentypen von Python angewendet werden?
Bhuro
38

Es geht nicht um tiefe oder flache Kopien, nichts, was Sie tun, ist tiefe Kopien.

Hier:

>>> new = original 

Sie erstellen einen neuen Verweis auf die Liste / das Diktat, auf die / das das Original verweist.

während hier:

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

Sie erstellen eine neue Liste / ein neues Diktat, das mit einer Kopie der Referenzen der im Originalcontainer enthaltenen Objekte gefüllt ist.

Lie Ryan
quelle
31

Nehmen Sie dieses Beispiel:

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

Lassen Sie uns nun einen Wert in der 'flachen' (ersten) Ebene ändern:

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

Ändern wir nun einen Wert eine Ebene tiefer:

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
Eumiro
quelle
8
no change in original, since ['a'] is an immutable integerDiese. Es beantwortet tatsächlich die gestellte Frage.
CivFan
7

Hinzufügen zu Kennytms Antwort. Wenn Sie eine flache Kopie von parent.copy () erstellen, wird ein neues Wörterbuch mit denselben Schlüsseln erstellt, aber die Werte werden nicht kopiert, auf die verwiesen wird. Wenn Sie parent_copy einen neuen Wert hinzufügen, wirkt sich dies nicht auf parent aus, da parent_copy ein neues Wörterbuch ist keine Referenz.

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

Der Hash (id) -Wert von parent [1] , parent_copy [1] ist identisch, was impliziert, dass [1,2,3] von parent [1] und parent_copy [1] unter id 140690938288400 gespeichert sind.

Der Hash von parent und parent_copy ist jedoch unterschiedlich, was bedeutet, dass es sich um unterschiedliche Wörterbücher handelt. Parent_copy ist ein neues Wörterbuch mit Werten, die auf Werte von parent verweisen

Vkreddy Komatireddy
quelle
5

"Neu" und "Original" sind unterschiedliche Diktate. Deshalb können Sie nur eines davon aktualisieren. Die Elemente werden flach kopiert, nicht das Diktat selbst.

Joril
quelle
2

Der Inhalt wird flach kopiert.

Wenn das Original also dictdas eine listoder andere enthält dictionary, werden sie durch Ändern des Originals oder seiner flachen Kopie (des listoder des dict) im anderen geändert.

Dschungeljäger
quelle
1

In Ihrem zweiten Teil sollten Sie verwenden new = original.copy()

.copyund =sind verschiedene Dinge.

朱骏 杰
quelle