42.7 Kopieren von Instanzen – copy 

Wie Sie bereits wissen, wird in Python bei einer Zuweisung nur eine neue Referenz auf ein und dieselbe Instanz erzeugt, anstatt eine Kopie der Instanz zu erzeugen.
Im folgenden Beispiel verweisen s und t auf dieselbe Liste, wie der Vergleich mit is offenbart:
>>> s = [1, 2, 3]
>>> t = s
>>> t is s
True
Dieses Vorgehen ist nicht immer erwünscht, weil Änderungen an der von s referenzierten Liste über Seiteneffekte auch t betreffen und umgekehrt.
Wenn beispielsweise eine Methode einer Klasse eine Liste zurückgibt, die auch innerhalb der Klasse verwendet wird, kann die Liste auch über die zurückgegebene Referenz verändert werden, was im Regelfall unerwünscht ist:
class MeineKlasse:
def __init__(self):
self.Liste = [1, 2, 3]
def getListe(self):
return self.Liste
def zeigeListe(self):
print(self.Liste)
Wenn wir uns nun mit der getListe-Methode eine Referenz auf die Liste zurückgeben lassen, können wir über einen Seiteneffekt das Attribut Liste der Instanz verändern:
>>> instanz = MeineKlasse()
>>> liste = instanz.getListe()
>>> liste.append(1337)
>>> instanz.zeigeListe()
[1, 2, 3, 1337]
Um dies zu verhindern, sollte die Methode getListe anstelle der intern verwalteten Liste selbst eine Kopie derselben zurückgeben.
An dieser Stelle kommt das Modul copy ins Spiel, das dazu gedacht ist, echte Kopien einer Instanz zu erzeugen. Für diesen Zweck bietet copy zwei Funktionen an: copy.copy und copy.deepcopy. Beide Methoden erwarten als Parameter die zu kopierende Instanz und geben eine Referenz auf eine Kopie von ihr zurück:[ 233 ](Natürlich kann eine Liste auch per Slicing kopiert werden. Das Modul copy erlaubt aber das Kopieren beliebiger Instanzen. )
>>> import copy
>>> s = [1, 2, 3]
>>> t = copy.copy(s)
>>> t
[1, 2, 3]
>>> t is s
False
Das Beispiel zeigt, dass t zwar die gleichen Elemente wie s enthält, aber trotzdem nicht auf dieselbe Instanz wie s referenziert, sodass der Vergleich mit is negativ ausfällt.
Der Unterschied zwischen copy.copy und copy.deepcopy besteht darin, wie mit Referenzen umgegangen wird, die die zu kopierenden Instanzen enthalten. Die Funktion copy.copy erzeugt zwar eine neue Liste, aber die Referenzen innerhalb der Liste verweisen trotzdem auf dieselben Elemente. Mit copy.deepcopy hingegen wird die Instanz selbst kopiert und anschließend rekursiv auch alle von ihr referenzierten Instanzen.
Wir veranschaulichen diesen Unterschied anhand einer Liste, die eine weitere Liste enthält:
>>> liste = [1, [2, 3]]
>>> liste2 = copy.copy(liste)
>>> liste2.append(4)
>>> liste2
[1, [2, 3], 4]
>>> liste
[1, [2, 3]]
Wie erwartet, verändert sich beim Anhängen des neuen Elements 4 an liste2 nicht die von liste referenzierte Instanz. Wenn wir aber die innere Liste [2, 3] verändern, betrifft dies sowohl liste als auch liste2:
>>> liste2[1].append(1337)
>>> liste2
[1, [2, 3, 1337], 4]
>>> liste
[1, [2, 3, 1337]]
Der is-Operator zeigt uns den Grund für dieses Verhalten: Bei liste[1] und liste2[1] handelt es sich um dieselbe Instanz:
>>> liste[1] is liste2[1]
True
Arbeiten wir stattdessen mit copy.deepcopy, wird die Liste inklusive aller enthaltenen Elemente kopiert:
>>> liste = [1, [2, 3]]
>>> liste2 = copy.deepcopy(liste)
>>> liste2[1].append(4)
>>> liste2
[1, [2, 3, 4]]
>>> liste
[1, [2, 3]]
>>> liste[1] is liste2[1]
False
Sowohl die Manipulation von liste2[1] als auch der is-Operator zeigen, dass es sich bei liste2[1] und liste[1] um verschiedene Instanzen handelt.
Zurück zum Eingangsbeispiel
Nun können wir unsere Beispielklasse MeineKlasse so anpassen, dass die Methode getListe eine Kopie der intern verwalteten Liste zurückgibt:
class MeineKlasse:
def __init__(self):
self.Liste = [1, 2, 3]
def getListe(self):
return copy.deepcopy(self.Liste)
def zeigeListe(self):
print(self.Liste)
Führen wir nun denselben Test-Code wie oben mit der neuen Klasse aus, ist der unerwünschte Seiteneffekt verschwunden:
>>> instanz = MeineKlasse()
>>> liste = instanz.getListe()
>>> liste.append(1337)
>>> instanz.zeigeListe()
[1, 2, 3]
Wir verwenden hier deepcopy, damit das Attribut Liste auch dann vor Seiteneffekten geschützt ist, wenn sich veränderbare Elemente in der Liste befinden.
[»] Hinweis
Es gibt Datentypen, die sowohl von copy.copy als auch von copy.deepcopy nicht wirklich kopiert, sondern nur ein weiteres Mal referenziert werden. Dazu zählen unter anderem Modulobjekte, Methoden, file-Objekte, socket-Instanzen und traceback-Instanzen.
[»] Hinweis
Beim Kopieren einer Instanz mithilfe des copy-Moduls wird das Objekt ein weiteres Mal im Speicher erzeugt. Dies kostet mehr Speicherplatz und Rechenzeit als eine einfache Zuweisung. Deshalb sollten Sie copy nur dann benutzen, wenn Sie tatsächlich eine echte Kopie brauchen.