Pythons `copy`-modul
Den här artikeln förklarar Pythons copy-modul.
Med fokus på skillnaderna mellan ytliga och djupa kopior ger vi en tydlig genomgång—från de grundläggande mekanismerna för objektkopiering till tillämpningar i anpassade klasser—samt praktiska exempel.
YouTube Video
Pythons copy-modul
Pythons copy-modul är standardmodulen för att hantera kopiering av objekt. Målet är att förstå skillnaden mellan ytliga och djupa kopior och kunna kontrollera kopieringsbeteendet för anpassade objekt.
Grunder: Vad är en ytlig kopia?
Här illustrerar vi hur ytliga kopior beter sig för föränderliga objekt som listor och ordböcker. En ytlig kopia duplicerar endast toppnivåobjektet och delar referenserna inuti.
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)I den här koden dupliceras listan på toppnivå, men de inre underlistorna delas genom referens, så shallow[1].append(99) återspeglas också i original. Detta är ett typiskt beteende för en ytlig kopia.
Vad är en djup kopia?
En djup kopia duplicerar rekursivt objektet och alla dess interna referenser och skapar ett oberoende objekt. Använd den när du vill duplicera komplexa nästlade strukturer på ett säkert sätt.
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)I detta exempel påverkar ändringar i deep inte original. deepcopy kopierar hela objektgrafen och ger en oberoende kopia.
Hur du avgör vilken du ska använda
Vilken kopiering du ska använda bör avgöras av objektets struktur och ditt syfte. Om du inte vill att ändringar i interna föränderliga element ska påverka originalobjektet är deepcopy lämpligt. Detta beror på att den rekursivt duplicerar hela objektet och skapar en helt oberoende kopia.
Om det å andra sidan räcker att duplicera endast objektet på toppnivå och du värderar hastighet och minneseffektivitet är copy (en ytlig kopia) mer lämplig.
- Du vill undvika att originalet ändras när du modifierar interna föränderliga element → använd
deepcopy. - Det räcker att duplicera endast toppnivån och du vill prioritera prestanda (hastighet/minne) → använd
copy(ytlig kopia).
Hantering av inbyggda och oföränderliga objekt
Oföränderliga objekt (t.ex. int, str och tupler vars innehåll är oföränderligt) behöver vanligtvis inte kopieras. copy.copy kan returnera samma objekt när det är lämpligt.
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)Eftersom kopiering av oföränderliga objekt ger liten effektivitetsvinst kan samma objekt återanvändas. Detta är sällan något du behöver oroa dig för i applikationsdesign.
Anpassade klasser: definiera __copy__ och __deepcopy__
Om standardkopieringen inte är vad du förväntar dig för din klass kan du tillhandahålla din egen kopieringslogik. Om du definierar __copy__ och __deepcopy__ kommer copy.copy och copy.deepcopy att använda dem.
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- Genom att implementera
__copy__och__deepcopy__kan du flexibelt kontrollera klasspecifikt kopieringsbeteende. Till exempel kan du hantera fall där vissa attribut ska referera till (dela) samma objekt, medan andra attribut ska dupliceras som helt nya objekt. - Argumentet
memosom skickas till__deepcopy__är en ordbok som registrerar objekt som redan har behandlats under rekursiv kopiering och förhindrar oändliga loopar orsakade av cirkulära referenser.
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)- I den här koden skapas variabeln
shallow_copyvia en 'ytlig kopia', så endast objektets toppnivå dupliceras, och de objekt som det refererar till internt (i det här fallet listanchildren) delas med ursprungsobjektetroot_node. Som ett resultat uppdateras den deladechildren-listan när du lägger till en ny nod ishallow_copy, och ändringen återspeglas även i innehållet iroot_node. - Å andra sidan skapas variabeln
deep_copyvia en 'djup kopia', så den interna strukturen dupliceras rekursivt, vilket ger ett träd som är helt oberoende avroot_node. Därför påverkas interoot_node, även om du lägger till en ny nod ideep_copy.
Cykliska referenser (rekursiva objekt) och betydelsen av memo
När en komplex objektgraf refererar till sig själv (cykliska referenser) använder deepcopy en memo-ordbok för att spåra objekt som redan kopierats och förhindra oändliga loopar.
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))- Internt använder
deepcopymemoför att hänvisa till objekt som redan duplicerats, vilket möjliggör säker kopiering även i närvaro av cykler. Du behöver en liknande mekanism när du utför rekursion manuellt.
copyreg och anpassad kopiering i serialiseringsstil (avancerat)
Om du vill registrera särskilt kopieringsbeteende i samverkan med bibliotek kan du använda standardmodulen copyreg för att registrera hur objekt konstrueras/återskapas. Detta är användbart för komplexa objekt och C-tilläggstyper.
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)- Med
copyregkan du ange återskapanderegler som påverkar bådepickleochdeepcopy. Det är ett avancerat API, och i de flesta fall räcker__deepcopy__.
Praktiska förbehåll och fallgropar
Det finns flera viktiga punkter för att säkerställa korrekt beteende när du använder modulen copy. Nedan förklarar vi vanliga fallgropar du kan stöta på under utveckling och hur du undviker dem.
- Prestanda
deepcopykan förbruka mycket minne och CPU, så överväg noga om det verkligen behövs. - Hantering av element du vill dela
Om du vill att vissa attribut, såsom stora cachar, ska förbli delade, undvik avsiktligt att kopiera deras referenser i
__deepcopy__. - Oföränderligt internt tillstånd Om du håller oföränderlig data internt kan kopiering vara onödig.
- Trådar och externa resurser Resurser som inte kan kopieras, såsom sockets och filhandtag, är antingen meningslösa att kopiera eller kommer att orsaka fel, så du måste undvika att kopiera dem i din design.
Praktiskt exempel: ett mönster för att säkert uppdatera ordböcker
Vid uppdatering av ett komplext konfigurationsobjekt använder detta exempel deepcopy för att skydda originalinställningarna.
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)Med deepcopy kan du säkert skapa härledda konfigurationer utan att riskera att skada standardinställningarna. Detta är särskilt användbart för nästlade föränderliga strukturer som konfigurationer.
Bästa praxis
För att använda modulen copy på ett säkert och effektivt sätt är det viktigt att ha följande praktiska riktlinjer i åtanke.
- Välj mellan ytlig och djup kopia baserat på om förändringar kommer att ske och deras omfattning.
- Implementera
__copy__och__deepcopy__i anpassade klasser för att uppnå förväntat beteende. - Eftersom djupa kopior är kostsamma, minska behovet av kopiering genom design när det är möjligt. Överväg oföränderlighet och explicita kloningsmetoder, bland andra tekniker.
- Vid hantering av cykliska referenser, utnyttja antingen
deepcopyeller tillhandahåll en memo-liknande mekanism manuellt. - Utforma så att externa resurser som filhandtag och trådar inte kopieras.
Slutsats
Modulen copy är standardverktyget för objektkopiering i Python. Genom att korrekt förstå skillnaderna mellan ytliga och djupa kopior och implementera anpassat kopieringsbeteende vid behov kan du duplicera säkert och förutsägbart. Genom att klargöra redan i designfasen om kopiering verkligen är nödvändig och vad som ska delas kan du undvika onödiga buggar och prestandaproblem.
Du kan följa med i artikeln ovan med hjälp av Visual Studio Code på vår YouTube-kanal. Vänligen kolla även in YouTube-kanalen.