Pythons `copy`-Modul
Dieser Artikel erklärt Pythons copy-Modul.
Mit Fokus auf die Unterschiede zwischen flachen und tiefen Kopien bieten wir eine klare Erklärung – von den grundlegenden Mechanismen der Objektduplizierung bis hin zu Anwendungen in benutzerdefinierten Klassen – samt praktischer Beispiele.
YouTube Video
Pythons copy-Modul
Pythons Modul copy ist das Standardmodul zur Handhabung der Objektduplizierung (Kopieren). Ziel ist es, den Unterschied zwischen flachen und tiefen Kopien zu verstehen und das Kopierverhalten für benutzerdefinierte Objekte steuern zu können.
Grundlagen: Was ist eine flache Kopie?
Hier veranschaulichen wir das Verhalten flacher Kopien für veränderliche Objekte wie Listen und Dictionaries. Eine flache Kopie dupliziert nur das Objekt der obersten Ebene und teilt die darin enthaltenen Referenzen.
1# Example: shallow copy with lists and dicts
2import copy
3
4# A nested list
5original = [1, [2, 3], 4]
6shallow = copy.copy(original)
7
8# Mutate nested element
9shallow[1].append(99)
10
11print("original:", original) # nested change visible in original
12print("shallow: ", shallow)In diesem Code wird die Liste der obersten Ebene dupliziert, aber die inneren Unterlisten werden per Referenz gemeinsam genutzt, sodass sich shallow[1].append(99) auch in original widerspiegelt. Dies ist ein typisches Verhalten einer flachen Kopie.
Was ist eine tiefe Kopie?
Eine tiefe Kopie dupliziert rekursiv das Objekt und alle seine intern referenzierten Objekte und erzeugt so ein unabhängiges Objekt. Verwenden Sie sie, wenn Sie komplexe verschachtelte Strukturen sicher duplizieren möchten.
1# Example: deep copy with nested structures
2import copy
3
4original = [1, [2, 3], {'a': [4, 5]}]
5deep = copy.deepcopy(original)
6
7# Mutate nested element of the deep copy
8deep[1].append(100)
9deep[2]['a'].append(200)
10
11print("original:", original) # original remains unchanged
12print("deep: ", deep)In diesem Beispiel wirken sich Änderungen an deep nicht auf original aus. deepcopy kopiert den gesamten Objektgraphen und erzeugt eine unabhängige Replik.
Wie man die richtige Variante wählt
Welche Kopie Sie verwenden, sollte von der Struktur des Objekts und Ihrem Zweck abhängen. Wenn Änderungen an inneren veränderlichen Elementen das Originalobjekt nicht beeinflussen sollen, ist deepcopy geeignet. Denn dabei wird das gesamte Objekt rekursiv dupliziert, wodurch eine völlig unabhängige Kopie entsteht.
Wenn hingegen das Duplizieren nur des Objekts der obersten Ebene ausreicht und Sie Geschwindigkeit sowie Speichereffizienz schätzen, ist copy (eine flache Kopie) besser geeignet.
- Sie möchten verhindern, dass sich das Original ändert, wenn Sie innere veränderliche Elemente modifizieren → verwenden Sie
deepcopy. - Nur die oberste Ebene zu duplizieren reicht aus und Sie möchten Leistung (Geschwindigkeit/Speicher) priorisieren → verwenden Sie
copy(flache Kopie).
Umgang mit eingebauten und unveränderlichen Objekten
Unveränderliche Objekte (z. B. int, str und Tupel mit unveränderlichem Inhalt) müssen in der Regel nicht kopiert werden. copy.copy kann gegebenenfalls dasselbe Objekt zurückgeben.
1# Example: copying immutables
2import copy
3
4a = 42
5b = copy.copy(a)
6print(a is b) # True for small ints (implementation detail)
7
8s = "hello"
9t = copy.deepcopy(s)
10print(s is t) # True (strings are immutable)Da das Kopieren unveränderlicher Objekte wenig Effizienzgewinn bringt, kann dasselbe Objekt wiederverwendet werden. Darüber müssen Sie sich im Anwendungsdesign selten Gedanken machen.
Benutzerdefinierte Klassen: __copy__ und __deepcopy__ definieren
Wenn das Standardkopieren nicht dem entspricht, was Sie für Ihre Klasse erwarten, können Sie eigene Kopierlogik bereitstellen. Wenn Sie __copy__ und __deepcopy__ definieren, verwenden copy.copy und copy.deepcopy diese.
1# Example: customizing copy behavior
2import copy
3
4class Node:
5 def __init__(self, value, children=None):
6 self.value = value
7 self.children = children or []
8
9 def __repr__(self):
10 return f"Node({self.value!r}, children={self.children!r})"
11
12 def __copy__(self):
13 # Shallow copy: create new Node, but reuse children list reference
14 new = self.__class__(self.value, self.children)
15 return new
16
17 def __deepcopy__(self, memo):
18 # Deep copy: copy value and deepcopy each child
19 new_children = [copy.deepcopy(child, memo) for child in self.children]
20 new = self.__class__(copy.deepcopy(self.value, memo), new_children)
21 memo[id(self)] = new
22 return new- Durch die Implementierung von
__copy__und__deepcopy__können Sie das klassenspezifische Kopierverhalten flexibel steuern. Sie können damit beispielsweise Fälle abdecken, in denen bestimmte Attribute auf dasselbe Objekt verweisen (es also teilen) sollen, während andere Attribute als vollständig neue Objekte dupliziert werden sollen. - Das an
__deepcopy__übergebene Argumentmemoist ein Dictionary, das während des rekursiven Kopierens bereits verarbeitete Objekte protokolliert und Endlosschleifen durch zirkuläre Referenzen verhindert.
1# Build tree
2root_node = Node('root', [Node('child1'), Node('child2')])
3
4shallow_copy = copy.copy(root_node) # shallow copy
5deep_copy = copy.deepcopy(root_node) # deep copy
6
7# Modify children
8# affects both root_node and shallow_copy
9shallow_copy.children.append(Node('child_shallow'))
10# affects deep_copy only
11deep_copy.children.append(Node('child_deep'))
12
13# Print results
14print("root_node:", root_node)
15print("shallow_copy:", shallow_copy)
16print("deep_copy:", deep_copy)- In diesem Code wird die Variable
shallow_copydurch eine 'flache Kopie' erstellt, sodass nur die oberste Ebene des Objekts dupliziert wird und die intern referenzierten Objekte (in diesem Fall die Listechildren) mit dem ursprünglichenroot_nodegemeinsam genutzt werden. Als Ergebnis wird beim Hinzufügen eines neuen Knotens zushallow_copydie gemeinsamechildren-Liste aktualisiert, und die Änderung spiegelt sich auch im Inhalt vonroot_nodewider. - Die variable
deep_copywird hingegen durch eine 'tiefe Kopie' erstellt, sodass die interne Struktur rekursiv dupliziert wird und ein vollständig vonroot_nodeunabhängiger Baum entsteht. Daher wirkt sich selbst das Hinzufügen eines neuen Knotens zudeep_copynicht aufroot_nodeaus.
Zyklische Referenzen (rekursive Objekte) und die Bedeutung von memo
Wenn ein komplexer Objektgraph auf sich selbst verweist (zyklische Referenzen), verwendet deepcopy ein memo-Dictionary, um bereits kopierte Objekte nachzuverfolgen und Endlosschleifen zu verhindern.
1# Example: recursive list and deepcopy memo demonstration
2import copy
3
4a = []
5b = [a]
6a.append(b) # a -> [b], b -> [a] (cycle)
7
8# deepcopy can handle cycles
9deep = copy.deepcopy(a)
10print("deep copy succeeded, length:", len(deep))- Intern verwendet
deepcopymemo, um auf bereits duplizierte Objekte zu verweisen und so sicheres Kopieren auch bei Zyklen zu ermöglichen. Einen ähnlichen Mechanismus benötigen Sie, wenn Sie die Rekursion manuell durchführen.
copyreg und serialisierungsähnliches benutzerdefiniertes Kopieren (fortgeschritten)
Wenn Sie spezielles Kopierverhalten in Abstimmung mit Bibliotheken registrieren möchten, können Sie das Standardmodul copyreg verwenden, um zu registrieren, wie Objekte (neu) konstruiert werden. Das ist nützlich für komplexe Objekte und C-Erweiterungstypen.
1# Example: using copyreg to customize pickling/copy behavior (brief example)
2import copy
3import copyreg
4
5class Wrapper:
6 def __init__(self, data):
7 self.data = data
8
9def reduce_wrapper(obj):
10 # Return callable and args so object can be reconstructed
11 return (Wrapper, (obj.data,))
12
13copyreg.pickle(Wrapper, reduce_wrapper)
14
15w = Wrapper([1, 2, 3])
16w_copy = copy.deepcopy(w)
17print("w_copy.data:", w_copy.data)- Mit
copyregkönnen Sie Rekonstruktionsregeln bereitstellen, die sowohlpickleals auchdeepcopybeeinflussen. Es ist eine fortgeschrittene API, und in den meisten Fällen reicht__deepcopy__aus.
Praktische Hinweise und Fallstricke
Es gibt mehrere wichtige Punkte, um beim Einsatz des Moduls copy korrektes Verhalten sicherzustellen. Im Folgenden erläutern wir häufige Fallstricke in der Entwicklung und wie Sie sie vermeiden.
- Leistung
deepcopykann erheblich Speicher und CPU verbrauchen; prüfen Sie daher sorgfältig, ob es notwendig ist. - Umgang mit Elementen, die geteilt werden sollen
Wenn bestimmte Attribute, etwa große Caches, gemeinsam genutzt bleiben sollen, vermeiden Sie in
__deepcopy__bewusst das Kopieren ihrer Referenzen. - Unveränderlicher interner Zustand Wenn Sie intern unveränderliche Daten halten, ist Kopieren möglicherweise überflüssig.
- Threads und externe Ressourcen Ressourcen, die nicht kopiert werden können, wie Sockets und Dateihandles, sind entweder sinnlos zu kopieren oder verursachen Fehler; vermeiden Sie daher das Kopieren dieser Ressourcen grundsätzlich im Design.
Praktisches Beispiel: Ein Muster zum sicheren Aktualisieren von Dictionaries
Beim Aktualisieren eines komplexen Konfigurationsobjekts verwendet dieses Beispiel deepcopy, um die ursprünglichen Einstellungen zu schützen.
1# Example: safely update a nested configuration using deepcopy
2import copy
3
4default_config = {
5 "db": {"host": "localhost", "ports": [5432]},
6 "features": {"use_cache": True, "cache_sizes": [128, 256]},
7}
8
9# Create a working copy to modify without touching defaults
10working = copy.deepcopy(default_config)
11working["db"]["ports"].append(5433)
12working["features"]["cache_sizes"][0] = 512
13
14print("default_config:", default_config)
15print("working:", working)Mit deepcopy können Sie gefahrlos abgeleitete Konfigurationen erstellen, ohne die Standardeinstellungen zu gefährden. Dies ist besonders nützlich für verschachtelte veränderliche Strukturen wie Konfigurationen.
Bewährte Vorgehensweisen
Um das Modul copy sicher und effektiv zu verwenden, ist es wichtig, die folgenden praktischen Richtlinien zu beachten.
- Wählen Sie zwischen flacher und tiefer Kopie je nachdem, ob Änderungen auftreten und welchen Umfang sie haben.
- Implementieren Sie
__copy__und__deepcopy__in benutzerdefinierten Klassen, um das erwartete Verhalten zu erreichen. - Da tiefe Kopien teuer sind, reduzieren Sie den Bedarf an Kopieren nach Möglichkeit schon im Design. Erwägen Sie Unveränderlichkeit und explizite Klonmethoden sowie andere Techniken.
- Beim Umgang mit zyklischen Referenzen nutzen Sie entweder
deepcopyoder stellen manuell einen memo-ähnlichen Mechanismus bereit. - Entwerfen Sie so, dass externe Ressourcen wie Datei-Handles und Threads nicht kopiert werden.
Fazit
Das Modul copy ist das Standardwerkzeug für die Objektduplizierung in Python. Wenn Sie die Unterschiede zwischen flachen und tiefen Kopien korrekt verstehen und bei Bedarf benutzerdefiniertes Kopierverhalten implementieren, können Sie sicher und vorhersehbar duplizieren. Wenn Sie bereits in der Designphase klären, ob Kopieren wirklich notwendig ist und was geteilt werden soll, vermeiden Sie unnötige Fehler und Performance-Probleme.
Sie können den obigen Artikel mit Visual Studio Code auf unserem YouTube-Kanal verfolgen. Bitte schauen Sie sich auch den YouTube-Kanal an.