Flache und tiefe Kopien in Python: copy() und deepcopy()

In Python kann man mit den Funktionen copy() und deepcopy() des copy-Moduls eine „flache“ (shallow) und „tiefe“ (deep) Kopie erstellen. Eine flache Kopie kann auch mit der copy()-Methode von Listen, Dictionaries usw. erstellt werden.

Einzelheiten findet man dazu in der Python-Dokumentation unter „copy — Shallow and deep copy operations“

Im Folgenden findet man eine Zusammenfassung der Unterschiede zwischen der Zuweisung an eine andere Variable, der flachen Kopie und der tiefen Kopie.

import copy

liste = [0, 1, [2, 3]]
liste_assign = liste # Zuweisung
liste_copy = liste.copy() # flache Kopie (shallow copy)
liste_deepcopy = copy.deepcopy(l) # tiefe Kopie (deep copy)

liste[1] = 100
liste[2][0] = 200
print(liste)

# [0, 100, [200, 3]]

print(liste_assign)
# [0, 100, [200, 3]]

print(liste_copy)
# [0, 1, [200, 3]]

print(liste_deepcopy)
# [0, 1, [2, 3]]

shallow copy und deep copy in Python

Die offizielle Python-Dokumentation beschreibt shallow copy und deep copy wie folgt:

Der Unterschied zwischen flachen und tiefen Kopien ist nur für zusammengesetzte Objekte relevant (Objekte, die andere Objekte enthalten, wie Listen oder Klaseninstanzen):

  • Eine flache Kopie konstruiert ein neues zusammengesetztes Objekt und fügt dann (soweit möglich) Verweise auf die im Original gefundenen Objekte ein.

  • Eine tiefe Kopie konstruiert ein neues zusammengesetztes Objekt und fügt dann rekursiv Kopien der im Original gefundenen Objekte in dieses ein.

Wenn Objekte in veränderlichen Objekten enthalten sind, wie z. B. Elemente in einer Liste oder Werte in einem Wörterbuch, erzeugt eine flache Kopie Referenzen auf die ursprünglichen Objekte, während eine tiefe Kopie neue Kopien der ursprünglichen Objekte erzeugt. Bei Referenzen verweisen die Elemente auf dasselbe Objekt, so dass sich die Änderung eines der Elemente auch auf das andere auswirkt.

Zuweisung an eine andere Variable

Zunächst betrachten wir, was passiert, wenn ein Objekt einer anderen Variablen zugewiesen wird.

Wenn ein veränderbares Objekt, z. B. eine Liste oder ein Wörterbuch, mehreren Variablen zugewiesen wird, wirkt sich die Aktualisierung einer Variablen (z. B. das Ändern, Hinzufügen oder Löschen von Elementen) auch auf die anderen Variablen aus.

liste_1 = [0, 1, [2, 3]]
liste_2 = liste_1

print(liste_1 is liste_2)
# True

liste_1[1] = 100
liste_1[2][0] = 200

print(liste_1)
# [0, 100, [200, 3]]

print(liste_2)
# [0, 100, [200, 3]]

print(liste_1 is liste_2)
# True

Wie im obigen Codebeispiel gezeigt, zeigt der is-Operator, dass sich die beiden Variablen sowohl vor als auch nach der Wertänderung auf dasselbe Objekt beziehen.

Um eine Kopie statt einer Referenz desselben Objekts zu erstellen, verwendet man die Methode copy() oder die unten beschriebenen Funktionen copy.copy() und copy.deepcopy().

Unveränderliche Objekte wie Zahlen (int, float) und Strings (str) können nicht aktualisiert werden. Wenn diese Objekt-Typen zugewiesen werden, beziehen sich die beiden Variablen zunächst auf dasselbe Objekt. Wenn jedoch eine Variable auf einen neuen Wert aktualisiert wird, wird sie zu einem separaten Objekt, während die andere Variable gleich bleibt.

i1 = 5
i2 = i1

print(i1 is i2)
# True

i1 += 100
print(i1)
# 105

print(i2)
# 5

print(i1 is i2)
# False

Shallow copy: copy(), copy.copy(), etc.

copy()-Methode von lists (Listen), dictionaries (Wörterbüchern), etc.

Die copy()-Methode ist für Listen, Wörterbücher usw. vorgesehen. Die copy()-Methode erstellt eine flache Kopie.

Eine flache Kopie erzeugt ein neues Objekt, das Verweise auf die Elemente des ursprünglichen Objekts enthält. Bei einer Liste beispielsweise ist die kopierte Liste ein neues Objekt, dessen Elemente jedoch Verweise auf dieselben Objekte der ursprünglichen Liste sind.

liste = [0, 1, [2, 3]]
liste_copy = liste.copy()

print(liste is liste_copy)
# False

print(liste[2] is liste_copy[2])
# True

Wenn die Elemente veränderbar sind, wirkt sich die Aktualisierung des einen Elements auch auf das andere aus. Bei einem unveränderlichen Element hingegen wird es, wenn sein Wert geändert wird, zu einem eigenen Objekt. Das entsprechende Element in der anderen Liste bleibt unverändert.

liste[1] = 100
liste_[2][0] = 200

print(liste_)
# [0, 100, [200, 3]]

print(liste_copy)
# [0, 1, [200, 3]]

print(liste[2] is liste_copy[2])
# True

Das Gleiche gilt nicht nur für eine Liste von Listen wie im obigen Beispiel, sondern auch für eine Liste von Wörterbüchern, ein verschachteltes Wörterbuch (ein Wörterbuch, das andere Wörterbücher als Werte enthält) und so weiter.

Slice

Slices für veränderliche Sequenztypen, wie z. B. Listen, erstellen ebenfalls flache Kopien.

Die Anwendung des Slice [:], das alle Elemente angibt, erzeugt beispielsweise eine flache Kopie der gesamten Liste.

liste = [0, 1, [2, 3]]
liste_whole_slice = l[:]

liste[1] = 100
liste[2][0] = 200

print(liste)
# [0, 100, [200, 3]]

print(liste_whole_slice)
# [0, 1, [200, 3]]

Bevor Python 3.3 die copy()-Methode für Listen einführte, wurde üblicherweise die [:]-Notation verwendet, um eine flache Kopie zu erstellen. Bei neuem Code ist es im Allgemeinen empfehlenswert, die copy()-Methode zu verwenden, um die eigenen Absichten zu verdeutlichen.

Ein Ausschnitt aus einem Teil der Sequenz erzeugt ebenfalls eine flache Kopie.

liste = [0, 1, [2, 3]]
liste_slice = l[1:]

print(liste_slice)
# [1, [2, 3]]

liste[1] = 100
liste[2][0] = 200

print(liste)
# [0, 100, [200, 3]]

print(liste_slice)
# [1, [200, 3]]

Wenn man eine tiefe Kopie erstellen muss, kann man die Funktion copy.deepcopy() auf das Slice anwenden.

list(), dict(), etc.

Man kann eine flache Kopie einer Liste oder eines Dictionaries erstellen, indem man die ursprüngliche Liste oder das ursprüngliche Dictionary an list() oder dict() übergeben.

liste = [0, 1, [2, 3]]
liste_list = list(liste)

liste[1] = 100
liste[2][0] = 200

print(liste)
# [0, 100, [200, 3]]

print(liste_list)
# [0, 1, [200, 3]]

copy.copy()

Man kann auch eine flache Kopie mit der Funktion copy() aus dem copy-Modul erstellen.

import copy

liste = [0, 1, [2, 3]]
liste_copy = copy.copy(liste)

liste[1] = 100
liste[2][0] = 200

print(liste)
# [0, 100, [200, 3]]

print(liste_copy)
# [0, 1, [200, 3]]

Man verwendet copy.copy(), wenn man eine flache Kopie eines Objekts erstellen muss, das keine copy()-Methode bereitstellt.

Deep copy: copy.deepcopy()

Um eine tiefe Kopie zu erstellen, verwendet man die Funktion deepcopy() aus dem Copy-Modul.

import copy

liste = [0, 1, [2, 3]]
liste_deepcopy = copy.deepcopy(liste)

print(liste is liste_deepcopy)
# False

print(liste[2] is liste_deepcopy[2])
# False

liste[1] = 100
liste[2][0] = 200

print(liste)
# [0, 100, [200, 3]]

print(liste_deepcopy)
# [0, 1, [2, 3]]

Bei einer tiefen Kopie werden tatsächliche Kopien der Objekte anstelle ihrer Referenzen eingefügt. Daher haben Änderungen an einem Objekt keine Auswirkungen auf das andere.

Hier ist ein Beispiel für die Anwendung der Funktion deepcopy() auf ein Slice:

liste = [0, 1, [2, 3]]
liste_slice_deepcopy = copy.deepcopy(liste[1:])

print(liste_slice_deepcopy)
# [1, [2, 3]]

liste[1] = 100
liste[2][0] = 200

print(liste)
# [0, 100, [200, 3]]

print(liste_slice_deepcopy)
# [1, [2, 3]]