Pythons `copy`-modul

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 memo som 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_copy via en 'ytlig kopia', så endast objektets toppnivå dupliceras, och de objekt som det refererar till internt (i det här fallet listan children) delas med ursprungsobjektet root_node. Som ett resultat uppdateras den delade children-listan när du lägger till en ny nod i shallow_copy, och ändringen återspeglas även i innehållet i root_node.
  • Å andra sidan skapas variabeln deep_copy via en 'djup kopia', så den interna strukturen dupliceras rekursivt, vilket ger ett träd som är helt oberoende av root_node. Därför påverkas inte root_node, även om du lägger till en ny nod i deep_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 deepcopy memo fö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 copyreg kan du ange återskapanderegler som påverkar både pickle och deepcopy. 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 deepcopy kan 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 deepcopy eller 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.

YouTube Video