Tiefes und flaches Kopieren

Einführung

Deep Sea Squid
Wie wir im letzten Kapitel "Datentypen und Variablen" gesehen haben, verhält sich Python beim Kopieren einfacher Datentypen wie Integer und Strings ungewöhnlich im Vergleich zu anderen Programmiersprachen.
In dem folgenden Code-Beispiel zeigt y zunächst nur auf den gleichen Speicherplatz wie x. Erst wenn der Wert von y verändert wird, erhält y einen eigenen Speicherplatz, wie wir im vorigen Kapitel gesehen haben.
>>> x = 3
>>> y = x
Aber auch wenn das obige Verhalten ungewöhlich im Vergleich zu anderen Programmiersprachen wie C, C++, Perl und anderen ist, so entsprechen die Resultate der Zuweisungen dennoch unseren Erwartungen. Kritisch wird es jedoch, wenn wir mutable Objekte wie Listen und Dictionarys kopieren wollen. Python legt nur dann echte Kopien an, wenn es unbedingt muss, d.h. dass es der Anwender, also der Programmierer, explizit verlangt. In diesem Kapitel wollen wir einige Probleme aufzeigen, die beim Kopieren von mutablen Objekten entstehen können, also z.B. beim kopieren von Listen und Dictionaries.

Kopieren einer Liste

>>> colours1 = ["red", "green"]
>>> colours2 = colours1
>>> colours2 = ["rouge", "vert"]
>>> print colours1
['red', 'green']
Kopieren einer einfachen Liste In dem obigen Code-Beispiel legen wir zuerst eine einfache Liste colours1 an, die wir dann in colours2 kopieren. Anschließend weisen wir colours2 eine neue Liste zu.

Es erstaunt wenig, dass die Werte von colours1 dabei unverändert bleiben. Wie bei dem Beispiel mit den Integer-Variablen im letzten Kapitel "Datentypen und Variablen" wird ein neuer Speicherbereich für colours2 angelegt, als dieser Variablen eine komplett neue Liste zugeordnet wird.
>>> colours1 = ["red", "green"]
>>> colours2 = colours1
>>> colours2[1] = "blue"
>>> colours1
['red', 'blue']
Kopieren einer einfachen Liste Wie sieht es jedoch aus, wenn nur einzelne Elemente geändert werden?
Um dies zu testen weisen wir dem zweiten Element von colours2 einen neuen Wert zu. Viele wird es nun erstauen, dass auch colours1 damit verändert wurde, obwohl man doch eine Kopie von colours1 gemacht zu haben glaubte. Die Erklärung ist, dass das zugehörige Objekt von colours2 nicht geändert wurde.

Kopie mit Teilbereichsoperator

Mit dem Teilbereichsoperator (slicing) kann man flache Listenstrukturen komplett kopieren, ohne dass es zu Seiteneffekten kommt, wie man im folgenden Beispiel sehen kann:

>>> liste1 = ['a','b','c','d']
>>> liste2 = liste1[:]
>>> liste2[1] = 'x'
>>> print liste2
['a', 'x', 'c', 'd']
>>> print liste1
['a', 'b', 'c', 'd']
>>> 
Sobald jedoch auch Unterlisten in der zu kopierenden Liste vorkommen, werden nur Zeiger auf diese Unterlisten kopiert.
>>> lst1 = ['a','b',['ab','ba']]
>>> lst2 = lst1[:]

Dieses Verhalten beim Kopieren veranschaulicht das folgende Bild:
Kopieren einer Liste, die Unterlisten enhält










Weist man nun zum Beispiel dem 0-ten Element einer der beiden Listen einen neuen Wert zu, führt dies nicht zu einem Seiteneffekt. Probleme gibt es erst, wenn man direkt eines der beiden Elemente der Unterliste verändert.
Um dies zu demonstrieren, ändern wir nun zwei Einträge in lst2:
>>> lst1 = ['a','b',['ab','ba']]
>>> lst2 = lst1[:]
>>> lst2[0] = 'c'
>>> lst2[2][1] = 'd'
>>> print(lst1)
['a', 'b', ['ab', 'd']]
>>> 

Man erkennt, dass man aber nicht nur die Einträge in lst2 geändert hat, sondern auch den Eintrag von lst1[2][1].
Dies liegt daran, dass in beiden Listen, also lst1 und lst2, das jeweils dritte Element nur ein Link auf eine physikalisch gleiche Unterliste ist. Diese Unterliste wurde nicht mit [:] mitkopiert.
Kopieren einer Liste, die Unterlisten enhält









Kopie mit der Methode deepcopy aus dem Modul copy

Abhilfe für das eben beschriebene Problem schafft das Modul "copy". Dieses Modul stellt die Methode "deepcopy" zur Verfügung, die das komplette Kopieren einer nicht flachen Listenstruktur erlaubt.

Das folgende Skript kopiert unser obiges Beispiel nun mit Hilfe dieser Methode:
from copy import deepcopy

lst1 = ['a','b',['ab','ba']]

lst2 = deepcopy(lst1)

lst2[2][1] = "d"
lst2[0] = "c";

print lst2
print lst1
Speichern wir das Skript unter deep_copy.py ab und rufen es mit "python deep_copy.py" auf, so erhalten wir folgende Ausgabe:
$ python deep_copy.py 
['c', 'b', ['ab', 'd']]
['a', 'b', ['ab', 'ba']]
Kopieren einer Liste mit Deep-Copy