Flache und tiefe Kopie

Einführung

Matrjoschkas

In diesem Kapitel werden wir uns mit der Frage befassen, wie Listen und verschachtelte Listen kopiert werden. Die Probleme, auf die wir stoßen werden, sind allgemeine Probleme veränderlicher Datentypen. Der Versuch, Listen zu kopieren, kann für Neulinge eine verblüffende Erfahrung sein. Zuvor möchten wir jedoch einige Erkenntnisse aus dem vorherigen Kapitel Datentypen und Variablen zusammenfassen. Python zeigt sogar ein seltsames Verhalten für die Anfänger der Sprache - im Vergleich zu einigen anderen traditionellen Programmiersprachen - beim Zuweisen und Kopieren einfacher Datentypen wie Ganzzahlen und Zeichenfolgen. Der Unterschied zwischen flachem und tiefem Kopieren ist nur für zusammengesetzte Objekte relevant, d. H. Objekte, die andere Objekte enthalten, wie Listen oder Klasseninstanzen.

Im folgenden Codeausschnitt zeigt y auf denselben Speicherort wie X. Wir können dies sehen, indem wir die Funktion id () auf x und y anwenden. Im Gegensatz zu "echten" Zeigern wie in C und C ++ ändern sich die Dinge jedoch, wenn wir y einen neuen Wert zuweisen. In diesem Fall erhält y einen separaten Speicherort, wie wir im Kapitel Datentypen und Variablen gesehen haben und im folgenden Beispiel sehen können ::

x = 3
y = x
print(id(x), id(y)) 
140720897565136 140720897565136
y = 4
print(id(x), id(y))
140720897565136 140720897565168
print(x,y)
3 4

Aber selbst wenn dieses interne Verhalten im Vergleich zu Programmiersprachen wie C, C ++, Perl oder Java seltsam erscheint, sind die beobachtbaren Ergebnisse der vorherigen Zuweisungen das, was wir erwartet haben, d. H. Wenn Sie sich die ID-Werte nicht ansehen. Es kann jedoch problematisch sein, wenn wir veränderbare Objekte wie Listen und Wörterbücher kopieren.

Python erstellt nur echte Kopien, wenn dies erforderlich ist, d. H. Wenn der Benutzer, der Programmierer, dies ausdrücklich verlangt.

Wir werden Ihnen die wichtigsten Probleme vorstellen, die beim Kopieren veränderlicher Objekte wie Listen und Wörterbücher auftreten können.

Variablen, die ein Objekt gemeinsam nutzen

Auf dieser Seite erfahren Sie, wie Sie Objekte, insbesondere Listen, kopieren. Trotzdem müssen Sie Geduld üben. Wir möchten vielen Anfängern etwas zeigen, das wie eine Kopie aussieht, aber nichts mit Kopien zu tun hat.

Farben1 = ["red", "blue"]
Farben2 = Farben1
print(Farben1, Farben2)
['red', 'blue'] ['red', 'blue']

Beide Variablen verweisen auf dasselbe Listenobjekt. Wenn wir uns die Identitäten der Variablen Farben1 und Farben2 ansehen, können wir sehen, dass beide Verweise auf dasselbe Objekt sind:

print(id(Farben1), id(Farben2))
2372364690120 2372364690120

Im obigen Beispiel wird Farben1 eine einfache Liste zugewiesen. Diese Liste ist eine sogenannte "flache Liste", da sie keine verschachtelte Struktur aufweist, d. H. Keine Unterlisten in der Liste enthalten sind. Im nächsten Schritt weisen wir Farben2 Farben2 zu.

Die Funktion id () zeigt uns, dass beide Variablen auf dasselbe Listenobjekt zeigen, d. H. Sie teilen dieses Objekt.

Deep Copy Detailed

Wir haben zwei Variablennamen Farben1 und Farben2, die wir als gelbe Ovale dargestellt haben. Das blaue Feld symbolisiert das Listenobjekt. Ein Listenobjekt besteht aus Verweisen auf andere Objekte. In unserem Beispiel verweist das Listenobjekt, auf das beide Variablen verweisen, auf zwei Zeichenfolgenobjekte, d. H. "rot" und "blau". Jetzt müssen wir untersuchen, was passieren wird, wenn wir nur ein Element der Liste von Farben oder Farben1 ändern.

Jetzt wollen wir sehen, was passiert, wenn wir Farben2 ein neues Objekt zuweisen. Wie erwartet bleiben die Werte von Farben1 unverändert. Wie in unserem Beispiel im Kapitel "Datentypen und Variablen" wurde ein neuer Speicherort für Farben2 zugewiesen , da wir dieser Variablen eine völlig neue Liste zugewiesen haben, dh ein neues Listenobjekt.

Farben1 = ["rot", "blau"]
Farben2 = Farben1
print(id(Farben1), id(Farben2))
2372363879240 2372363879240
Farben2 = "grün"
print(Farben1)
['rot', 'blau']
print(Farben1)
['rot', 'blau']
print(Farben2)
grün

Wir haben der Variablen 'Farben2' ein neues Objekt zugewiesen. Im folgenden Code ändern wir das Listenobjekt intern, indem wir dem zweiten Element der Liste einen neuen Wert zuweisen.

Farben1 = ["rot", "blau"]
Farben2 = Farben1
Farben2[1] = "grün"

Copying a simple list

Mal sehen, was im vorherigen Code im Detail passiert ist. Wir haben dem zweiten Element von Farben2 einen neuen Wert zugewiesen, d. H. Dem Element mit dem Index 1. Viele Anfänger werden überrascht sein, da die Liste von Farben1 ebenfalls "automatisch" geändert wurde. Natürlich haben wir keine zwei Listen: Wir haben nur zwei Namen für dieselbe Liste!

Die Erklärung ist, dass wir der Variablen Farben2 kein neues Objekt zugewiesen haben. Wir haben das Objekt, auf das Farben2 verweist, intern geändert oder wie es normalerweise als" an Ort und Stelle "bezeichnet wird. Beide Variablen Farben1 und Farben zeigen immer noch auf dasselbe Listenobjekt.

Kopieren von Listen

Wir sind endlich beim Thema Kopieren von Listen angekommen. Die Klasse list bietet zu diesem Zweck die Methode copy an. Obwohl der Name klar zu sein scheint, hat dieser Weg auch einen Haken.

Der Haken ist zu sehen, wenn wir die Hilfe für copy verwenden:

help(list.copy)
Help on method_descriptor:
copy(self, /)
    Return a shallow copy of the list.

Viele Anfänger übersehen hier das Wort "flach". help sagt uns, dass eine neue Liste mit der Methode copy erstellt wird. Diese neue Liste ist eine "flache" Kopie der ursprünglichen Liste.

Im Prinzip ist das Wort "flach" in dieser Definition unnötig oder sogar irreführend.

Zuerst sollten Sie sich daran erinnern, was eine Liste in Python ist. Eine Liste in Python ist ein Objekt, das aus einer geordneten Folge von Verweisen auf Python-Objekte besteht. Das Folgende ist eine Liste von Zeichenfolgen:

Eine Liste von Zeichenfolgen (Vornamen)

Die Liste, auf die sich die Variable Vornamen bezieht, ist eine Liste von Zeichenfolgen. Grundsätzlich ist das Listenobjekt nur das blaue Feld mit den Pfeilen, d. H. Den Verweisen auf die Zeichenfolgen. Die Zeichenfolgen selbst sind nicht Teil der Liste.

Vornamen = ['Kevin', 'Jamina', 'Lars', 'Maria']

Im vorherigen Beispiel ist die Liste der Vornamen Vornamen homogen, d. H. Sie besteht nur aus Zeichenfolgen, sodass alle Elemente den gleichen Datentyp haben. Sie sollten sich jedoch der Tatsache bewusst sein, dass sich die Referenzen einer Liste auf beliebige Objekte beziehen können. Die folgende Liste was-auch-immer ist eine so allgemeinere Liste:

Eine Liste mit beliebigen Python-Objekten

Wenn eine Liste kopiert wird, kopieren wir die Referenzen. In unseren Beispielen sind dies die blauen Kästchen, auf die mit Vornamen und mit was auch immer verwiesen wird. Die Auswirkungen davon werden im folgenden Unterkapitel gezeigt.

Probleme beim Kopieren von Listen

Das Kopieren von Listen kann leicht missverstanden werden, und dieses Missverständnis kann zu unangenehmen Fehlern führen. Wir werden dies in Form einer etwas anderen Liebesgeschichte präsentieren.

Konstanz Seestrasse

Stellen Sie sich eine Person vor, die in der Stadt Konstanz lebt. Die Stadt liegt im Süden Deutschlands am Bodensee. Der Rhein, der in den Schweizer Alpen beginnt, fließt durch den Bodensee und verlässt ihn erheblich größer. Diese Person namens Swen lebt in der "Seestrasse", einer der teuersten Straßen von Konstanz. Seine Wohnung hat einen direkten Blick auf den See und die Stadt Konstanz. Wir haben einige Informationen über Swen in die folgende Listenstruktur aufgenommen:

Person1 = ["Swen", ["Seestrasse", "Konstanz"]]

Kopieren einer verschachtelten Liste, Teil1

Eines schönen Tages trifft die gutaussehende Sarah Swen, ihren charmanten Prinzen. Um die Geschichte kurz zu machen: Liebe auf den ersten Blick und sie zieht bei Swen ein.

Jetzt liegt es an uns, einen Datensatz für Sarah zu erstellen. Wir sind faul und werden Swens Daten recyceln, weil sie jetzt mit ihm in derselben Wohnung leben wird.

Wir werden Swens Daten kopieren und den Namen in Sarah ändern:

Person2 = Person1.copy()
Person2[0] = "Sarah"
print(Person2)
['Sarah', ['Seestrasse', 'Konstanz']]

Kopieren einer verschachtelten Liste, Teil2

Sie leben lange Zeit in perfekter Harmonie und tiefer Liebe, sagen wir ein ganzes Wochenende. Sie merkt plötzlich, dass Swen ein Monster ist: Er befleckt die Butter mit Marmelade und Honig und noch schlimmer, er breitet seine abgenutzten Socken im Schlafzimmer aus. Es dauert nicht lange, bis sie eine entscheidende Entscheidung trifft: Sie wird ihn und die Traumwohnung verlassen.

Sie zieht in eine Straße namens Bücklestrasse. Ein Wohngebiet, das bei weitem nicht so schön ist, aber zumindest ist sie vom Monster entfernt.

Wie können wir diesen Schritt aus Python-Sicht arrangieren?

Die Straße kann mit person2[1][0] erreicht werden. Also setzen wir dies auf den neuen Ort:

Person2[1][0] = "Bücklestraße"

Wir können sehen, dass Sarah erfolgreich an den neuen Standort gezogen ist:

print(Person2)
['Sarah', ['Bücklestraße', 'Konstanz']]

Ist das das Ende unserer Geschichte? Wird sie Swen nie wieder sehen? Lassen Sie uns Swens Daten überprüfen:

print(Person1)
['Swen', ['Bücklestraße', 'Konstanz']]

Swen ist anhänglich. Sie kann ihn nicht loswerden! Dies könnte für einige ziemlich überraschend sein. Warum ist es so? Die Liste Person1 besteht aus zwei Verweisen: Einer auf ein String-Objekt (" Swen ") und der andere auf eine verschachtelte Liste (die Adresse['Seestrasse', 'Konstanz']. Wenn wir copy verwendet haben, haben wir nur diese Referenzen kopiert. Dies bedeutet, dass sowohl person1[1] als auch Person2[1]auf dasselbe Listenobjekt verweisen. Wenn wir Ändern Sie diese verschachtelte Liste, sie ist in beiden Listen sichtbar.

Copy Nested lists

deepcopy aus der Modulkopie

Eine Lösung für das beschriebene Problem bietet das Modul copy. Dieses Modul stellt die Methode "Deepcopy" bereit, die eine vollständige oder tiefe Kopie einer beliebigen Liste ermöglicht, d. H. Flache und andere Listen.

Wiederholen wir das vorherige Beispiel mit der Funktion deepcopy:

from copy import deepcopy
Person1 = ["Swen", ["Seestrasse", "Konstanz"]]
Person2 = deepcopy(Person1)
Person2[0] = "Sarah"

Danach sieht die Implementierungsstruktur folgendermaßen aus:

Verschachtelte Listen kopieren

Wir können sehen, dass die verschachtelte Liste mit der Adresse ebenfalls kopiert wurde. Wir sind jetzt gut vorbereitet auf Sarahs flüchtigen Schritt.

Person2[1][0] = "Bücklestrasse"

Sie ist erfolgreich ausgezogen und hat Swen endgültig verlassen, wie wir im Folgenden sehen können:

print(Person1, Person2)
['Swen', ['Seestrasse', 'Konstanz']] ['Sarah', ['Bücklestrasse', 'Konstanz']]

Aus Gründen der Übersichtlichkeit stellen wir hier auch ein Diagramm zur Verfügung.

Copy nested lists

Mit der ID-Funktion können wir feststellen, dass die Unterliste kopiert wurde, da sich id (Person1[1])von id (Person2[1]) unterscheidet. Eine interessante Tatsache ist, dass die Zeichenfolgen nicht kopiert werden: Person1[0] und Person2[0] verweisen auf dieselbe Zeichenfolge.

print(id(Person1[1]), id(Person2[1]))
2372365051464 2372365051848