Das Kopieren von Objekten ist ein grundlegendes Konzept in Python, dessen Missverständnis oft zu schwer auffindbaren Fehlern führt. Wenn Sie ein Objekt ändern und sich unerwartet ein anderes ebenfalls ändert, liegt das Problem höchstwahrscheinlich in der Art und Weise, wie es kopiert wurde. Lassen Sie uns dieses Thema ein für alle Mal klären.
🔎 Warum ist das Kopieren von Objekten überhaupt wichtig?
In Python sind Variablen keine „Boxen“, die Daten enthalten, sondern eher „Etiketten“ oder Referenzen (Verweise), die auf Objekte im Speicher zeigen. Wenn Sie eine einfache Zuweisung durchführen, wie hier:
liste_a = [1, 2, 3]
liste_b = liste_a
Erstellen Sie keine neue Liste. Sie erstellen lediglich eine weitere Referenz (liste_b), die auf genau dasselbe Listenobjekt zeigt.
👉 Jede Änderung an liste_a spiegelt sich sofort in liste_b wider (und umgekehrt), weil es sich um dasselbe Objekt handelt.
Analogie: Stellen Sie sich vor, Sie haben ein Google-Dokument. Wenn Sie einem Freund einen Link dazu senden, wird er Ihr Originaldokument bearbeiten. Das ist eine Zuweisung. Wenn Sie jedoch „Datei“ → „Kopie erstellen“ wählen, wird ein völlig unabhängiges Dokument erstellt. Das copy-Modul in Python existiert genau dafür, um solche unabhängigen Kopien zu erstellen.
⚙️ Zwei Arten des Kopierens
Das copy-Modul stellt zwei Schlüsselfunktionen zur Verfügung:
copy.copy()— erstellt eine oberflächliche Kopie (shallow copy).copy.deepcopy()— erstellt eine tiefe Kopie (deep copy).
Der Hauptunterschied zwischen ihnen liegt darin, wie sie mit verschachtelten Objekten umgehen (wie Listen in Listen oder Dictionaries in Dictionaries).
🟦 Oberflächliche Kopie (copy.copy())
copy.copy() erstellt ein neues Container-Objekt auf der obersten Ebene, aber anstatt die verschachtelten Objekte zu kopieren, fügt es nur Referenzen auf die Originalobjekte in den neuen Container ein.
import copy
original_liste = [1, 2, [3, 4]]
flache_kopie = copy.copy(original_liste)
# Ändern der verschachtelten Liste im Original
original_liste[2][0] = 99
print(f"Original: {original_liste}") # Ausgabe: Original: [1, 2, [99, 4]]
print(f"Flache Kopie: {flache_kopie}") # Ausgabe: Flache Kopie: [1, 2, [99, 4]]
Wie Sie sehen, hat die Änderung der verschachtelten Liste sowohl das Original als auch die Kopie beeinflusst, da sie sich diese verschachtelte Liste teilen.
ASCII-Diagramm:
original_liste → [ 1, 2, ───► innere_liste ]
flache_kopie → [ 1, 2, ───► innere_liste ]
innere_liste → [ 99, 4 ]
🟩 Tiefe Kopie (copy.deepcopy())
copy.deepcopy() löst das Problem der geteilten Referenzen. Diese Funktion durchläuft rekursiv das gesamte Objekt und erstellt vollständige, unabhängige Kopien von allem, was sie findet, einschließlich aller verschachtelten Objekte.
import copy
original_liste = [1, 2, [3, 4]]
tiefe_kopie = copy.deepcopy(original_liste)
# Ändern der verschachtelten Liste im Original
original_liste[2][0] = 99
print(f"Original: {original_liste}") # Ausgabe: Original: [1, 2, [99, 4]]
print(f"Tiefe Kopie: {tiefe_kopie}") # Ausgabe: Tiefe Kopie: [1, 2, [3, 4]]
Jetzt ist die tiefe_kopie vollständig unabhängig.
ASCII-Diagramm:
original_liste → [ 1, 2, ───► liste_a ]
tiefe_kopie → [ 1, 2, ───► liste_b ]
liste_a → [ 99, 4 ]
liste_b → [ 3, 4 ]
📑 Spickzettel: copy.copy() vs. copy.deepcopy()
| Objekttyp | Beispiel | copy.copy() (oberflächlich) | copy.deepcopy() (tief) |
|---|---|---|---|
| list (Liste) | [1, 2, [3, 4]] | Neue Liste, aber die verschachtelte Liste [3, 4] ist geteilt | Eine vollständig unabhängige Kopie; die verschachtelte Liste wird ebenfalls kopiert |
| dict (Dictionary) | {"a": 1, "b": {"c": 2}} | Neues Dictionary, aber das verschachtelte {"c": 2} ist geteilt | Ein unabhängiges Dictionary und alle verschachtelten Dictionaries |
| set (Menge) | {1, 2, (3, 4)} | Neue Menge; verschachtelte veränderbare Elemente wären geteilt | Neue Menge; alle verschachtelten Elemente werden rekursiv kopiert |
| tuple (Tupel) | (1, 2, [3, 4]) | Gibt dasselbe Tupel zurück, aber die verschachtelte Liste ist geteilt | Ein neues Tupel mit einer Kopie der verschachtelten Liste |
| str, int, float, bool | "hallo", 42, 3.14, True | Gibt dasselbe Objekt zurück (sie sind unveränderlich, Kopieren ist unnötig) | Dasselbe wie bei der oberflächlichen Kopie |
| Benutzerdefinierte Klasse | Knoten(1, Knoten(2)) | Neues Objekt auf oberster Ebene; verschachtelte Objekte sind geteilt | Eine vollständig unabhängige Kopie des Objekts und aller verschachtelten Objekte |
Objekt mit __slots__ | Eine Klasse mit __slots__ | Nur das Objekt der obersten Ebene wird kopiert | Alle verschachtelten Objekte werden kopiert, wenn sie das Kopieren unterstützen |
| Datei / Socket / Stream | open("datei.txt") | Fehler (nicht kopierbares Objekt) | Fehler (nicht kopierbares Objekt) |
🧪 Live-Beispiele zur Tabelle
📌 list
import copy
a = [1, 2, [3, 4]]
b = copy.copy(a)
c = copy.deepcopy(a)
a[2][0] = 99
print(f"copy: {b}") # [1, 2, [99, 4]]
print(f"deepcopy: {c}") # [1, 2, [3, 4]]
📌 dict
import copy
a = {"a": 1, "b": {"c": 2}}
b = copy.copy(a)
c = copy.deepcopy(a)
a["b"]["c"] = 42
print(f"copy: {b}") # {'a': 1, 'b': {'c': 42}}
print(f"deepcopy: {c}") # {'a': 1, 'b': {'c': 2}}
📌 set
import copy
a = {1, 2, (3, 4)} # Mengen können nur unveränderliche Typen enthalten
b = copy.copy(a)
c = copy.deepcopy(a)
print(b == c) # True (aber es sind zwei unabhängige Objekte im Speicher)
📌 tuple
import copy
a = (1, 2, [3, 4]) # Das Tupel ist unveränderlich, sein Listenelement jedoch nicht
b = copy.copy(a)
c = copy.deepcopy(a)
a[2][0] = 99
print(f"copy: {b}") # (1, 2, [99, 4])
print(f"deepcopy: {c}") # (1, 2, [3, 4])
📌 Benutzerdefinierte Klasse
import copy
class Knoten:
def <strong>init</strong>(self, wert, kind=None):
self.wert = wert
self.kind = kind
a = Knoten(1, Knoten(2))
b = copy.copy(a)
c = copy.deepcopy(a)
a.kind.wert = 99
print(f"copy: {b.kind.wert}") # 99
print(f"deepcopy: {c.kind.wert}") # 2
📌 Anpassen des Verhaltens: `__copy__` und `__deepcopy__`
Sie können steuern, wie Ihre eigenen Objekte kopiert werden, indem Sie spezielle Methoden in ihrer Klasse definieren.
import copy
class EigeneKlasse:
def <strong>init</strong>(self, x):
self.x = x
def <strong>copy</strong>(self):
print("Aufruf von <strong>copy</strong>!")
# Gibt eine neue Instanz zurück, aber mit derselben Referenz auf interne Daten
return EigeneKlasse(self.x)
def <strong>deepcopy</strong>(self, memo):
# memo ist ein Dictionary, um bereits kopierte Objekte zu verfolgen (verhindert Rekursion)
print("Aufruf von <strong>deepcopy</strong>!")
# Erstellt eine komplett neue Kopie, einschließlich der verschachtelten Daten
return EigeneKlasse(copy.deepcopy(self.x, memo))
obj = EigeneKlasse([1, 2])
c1 = copy.copy(obj) # Ausgabe: Aufruf von <strong>copy</strong>!
c2 = copy.deepcopy(obj) # Ausgabe: Aufruf von <strong>deepcopy</strong>!
```
⚡ Leistung und Fallstricke
- Geschwindigkeit:
copy.copy()ist erheblich schneller, da es nicht die gesamte Datenstruktur rekursiv durchläuft.copy.deepcopy()kann bei großen und tief verschachtelten Objekten sehr langsam sein. - Nicht kopierbare Objekte: Systemressourcen wie geöffnete Dateien, Sockets oder Streams können nicht kopiert werden. Der Versuch, dies zu tun, führt zu einem Fehler.
- Zyklische Referenzen:
deepcopyist intelligent genug, um mit zyklischen Referenzen umzugehen (wenn ObjektAaufBverweist undBzurück aufA), ohne in eine Endlosschleife zu geraten.
Wann was verwenden: Die wichtigsten Regeln
- Verwenden Sie eine oberflächliche Kopie (
copy.copy()), wenn Ihr Objekt nur unveränderliche Daten enthält oder wenn Sie absichtlich möchten, dass die Kopie und das Original verschachtelte Objekte teilen. Dies ist schnell und effizient. - Verwenden Sie eine tiefe Kopie (
copy.deepcopy()), wenn Sie eine vollständige, 100% unabhängige Kopie des Originals benötigen, insbesondere bei der Arbeit mit komplexen Datenstrukturen (Listen, Dictionaries, Klasseninstanzen). - Für unveränderliche Objekte (Zahlen, Zeichenketten, Tupel ohne veränderbare Elemente) ist das Kopieren im Allgemeinen sinnlos – Python optimiert deren Verwendung ohnehin.
- Denken Sie daran, dass Sie den Prozess mit
__copy__und__deepcopy__anpassen können, um die volle Kontrolle zu haben.