17.6 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 auch t über Seiteneffekte 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, womit das Kapselungsprinzip verletzt wäre:
class MeineKlasse(object): def __init__(self): self.__Liste = [1, 2, 3] def getListe(self): return self.__Liste def zeigeListe(self): print self.__Liste
Wenn wir uns nun mittels der getListe-Methode eine Referenz auf die Liste zurückgeben lassen, können wir über einen Seiteneffekt das private 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 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: [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.
Es gibt allerdings 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 Modul-Objekte, 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 erheblich mehr Speicherplatz und Rechenzeit als eine einfache Zuweisung. Deshalb sollten Sie copy wirklich nur dann benutzen, wenn Sie tatsächlich eine echte Kopie brauchen. |