Der Unterschied von copy() und deepcopy() in Python

In Python steht das Modul copy() zur Verfügung. Damit kann eine sogenannte flache Kopie (shallow copy) erzeugt werden. Darüber hinaus existiert auch noch deepcopy() für das Erstellen einer tiefen Kopie (deep copy). Dieser Beitrag befasst sich mit der Frage, was es damit auf sich hat.

Anwendung des Zuweisungsoperators

Dazu schauen wir uns zunächst folgende Beispiele an, bei denen weder copy() noch deepcopy() zum Einsatz kommen, sondern stattdessen eine Zuweisung mit dem Zuweisungsoperator = erfolgt:

>>> a = 50
>>> b = a
>>> id(a) == id(b)
True

Hier haben wir zwei Variablen a und b, die das gleiche (Integer-)Objekt referenzieren. Interessant wird es, wenn jetzt eine Operation — zum Beispiel eine Addition — durchgeführt wird:

>>> a = a + 50
>>> print(a)
100
>>> print(b)
50
>>> id(a) == id(b)
False

Jetzt referenzieren a und b nicht mehr das gleiche Objekt. Stattdessen hat a eine neue Speicheradresse erhalten.

Im nächsten Beispiel untersuchen wir, ob es sich bei Listen ebenso verhält:

>>> list_a = [10, 20, 30, 40]
>>> list_b = list_a
>>> id(list_a) == id(list_b)
True

Zunächst sieht man keinen Unterschied zum ersten Beispiel, d.h. list_a und list_b referenzieren das gleiche Objekt.

Aber jetzt wird es spannend. Wie verhält es sich hier, wenn list_a verändert wird?

>>> list_a[-1] = 4000
>>> print(list_a)
[10, 20, 30, 4000]
>>> print(list_b)
[10, 20, 30, 4000]

Das Verändern des letzten Werts in der ersten Liste führt dazu, dass die zweite Liste ebenso geändert wird. Denn beide Listen referenzieren weiterhin das gleiche (Listen-)Objekt:

>>> id(list_a) == id(list_b)
True

Bei Listen Verhält es sich also anders. Es wird hierbei kein neuer Speicherplatz belegt.

Und damit lässt sich festhalten, dass durch die Anwendung des Zuweisungsoperators keine Kopie erzeugt wird. In der Python-Dokumentation heißt es dementsprechend:

Assignment statements in Python do not copy objects, they create bindings between a target and an object.

Flache Kopie versus tiefe Kopie

Nachdem wir betrachtet haben, was bei Anwendung des Zuweisungsoperators passiert, sehen wir uns nun copy() und deepcopy() an. Zunächst sei gesagt, dass der Unterschied zwischen flachen und tiefen Kopien nur bei zusammengesetzten Objekten (compound objects) von Bedeutung ist. Dabei handelt es sich um verschachtelte Datenstrukturen, beispielsweise einer Liste mit verschiedenen Listen:

>>> import copy

>>> list_a = [[10, 20, 30], [100, 200, 300]]
>>> list_b = copy.copy(list_a)

>>> print(list_a)
[[10, 20, 30], [100, 200, 300]]
>>> print(list_b)
[[10, 20, 30], [100, 200, 300]]

Nach dem Kopieren mit copy() haben beide Listen den gleichen Inhalt, was nicht weiter erstaunt. Aber die beiden Listen referenzieren nicht das gleiche Objekt:

>>> id(list_a) == id(list_b)
False

Mit anderen Worten: Bei der Anwendung von copy() wird tatsächlich eine Kopie angelegt. Dies eröffnet die Möglichkeit, die kopierte Liste zu ändern, ohne gleichzeitig die ursprüngliche Liste zu ändern. In der Python-Dokumentation heißt es dementsprechend:

For collections that are mutable or contain mutable items, a copy is sometimes needed so one can change one copy without changing the other.

Bei der Anwendung von copy() treffen wir aber auf folgende Besonderheit:

>>> list_a[-1][-1] = 1

>>> print(list_a)
[[10, 20, 30], [100, 200, 1]]
>>> print(list_b)
[[10, 20, 30], [100, 200, 1]]

Hier wird der letzte Wert in der zweiten (inneren) Liste von list_a von 300 zu 1 geändert. Obwohl list_a und list_b zwei unterschiedliche Objekte referenzieren, führt die Änderung in der ersten Liste ebenfalls zu einer Änderung in der zweiten Liste!

Das hängt damit zusammen, dass mit copy() zwar ein neue Objekt erstellt wird, aber die Referenzen des ursprünglichen Objekts in das neue Objekt eingefügt werden.

Daher lässt sich sagen, dass eine flache Kopie nur dann sinnvoll ist, wenn man nicht mit zusammengesetzten Objekten arbeitet.

Stattdessen sollte man in diesem Fall eine tiefe Kopie mit deepcopy() erstellen. Denn in diesem Fall werden ebenfalls die inneren Objekte kopiert, wie folgendes Beispiel zeigt:

>>> import copy

>>> list_a = [[10, 20, 30], [100, 200, 300]]
>>> list_b = copy.deepcopy(list_a)

>>> list_a[-1][-1] = 1

>>> print(list_a)
[[10, 20, 30], [100, 200, 1]]
>>> print(list_b)
[[10, 20, 30], [100, 200, 300]]

Man erhält mit deepcopy() also eine „echte“ Kopie, so dass hier ein Wert in der inneren Liste der ursprünglichen Liste geändert werden kann, ohne dass sich dies auf die kopierte Liste auswirken würde.

Fazit

Wenn es — wie am Anfang dieses Beitrags gezeigt — um nicht veränderbare Datentypen geht, muss man sich keine Gedanken machen. Nach der Zuweisung b = a können Operationen (im obigen Beispiel eine Addition) durchgeführt werden, ohne dass dies auf das andere Objekt durchgreifen würde.

Bei veränderbaren Datentypen muss man hingegen dann Vorsicht walten lassen, wenn zusammengesetzte Objekte vorliegen. Denn in diesem Fall liegen bei der Anwendung von copy() bei den geschachtelten Objekten Referenzen auf gleiche Objekte vor.