Pythons `copy`-modul
Denne artikkelen forklarer Pythons copy-modul.
Med fokus på forskjellene mellom grunne og dype kopier gir vi en klar forklaring—fra de grunnleggende mekanismene for objektkopiering til bruk i egendefinerte klasser—sammen med praktiske eksempler.
YouTube Video
Pythons copy-modul
Pythons copy-modul er standardmodulen for håndtering av objektduplisering (kopiering). Målet er å forstå forskjellen mellom grunne og dype kopier og kunne kontrollere kopieringsatferd for egendefinerte objekter.
Grunnleggende: Hva er en grunn kopi?
Her illustrerer vi hvordan grunne kopier oppfører seg for muterbare objekter som lister og ordbøker. En grunn kopi dupliserer bare toppnivåobjektet og deler referansene på innsiden.
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 denne koden blir listen på øverste nivå duplisert, men de indre underlistene deles ved referanse, så shallow[1].append(99) gjenspeiles også i original. Dette er typisk oppførsel for en grunn kopi.
Hva er en dyp kopi?
En dyp kopi dupliserer objektet rekursivt og alle dets interne referanser, og produserer et uavhengig objekt. Bruk den når du vil duplisere komplekse, nestede strukturer på en trygg måte.
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 dette eksempelet påvirker endringer i deep ikke original. deepcopy kopierer hele objektgrafen og gir en uavhengig kopi.
Slik velger du hvilken du skal bruke
Valget av kopi bør bestemmes av objektets struktur og ditt formål. Hvis du ikke vil at endringer i interne muterbare elementer skal påvirke originalobjektet, er deepcopy passende. Dette er fordi den dupliserer hele objektet rekursivt og skaper en helt uavhengig kopi.
Hvis det på den annen side er nok å duplisere bare toppnivåobjektet og du vektlegger hastighet og minneeffektivitet, er copy (en grunn kopi) mer egnet.
- Du vil unngå at originalen endres når du modifiserer interne muterbare elementer → bruk
deepcopy. - Det er nok å duplisere kun toppnivået og du vil prioritere ytelse (hastighet/minne) → bruk
copy(grunn kopi).
Håndtering av innebygde og uforanderlige objekter
Uforanderlige objekter (f.eks. int, str og tupler hvis innhold er uforanderlig) trenger vanligvis ikke å kopieres. copy.copy kan returnere det samme objektet når det er hensiktsmessig.
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)Siden kopiering av uforanderlige objekter gir liten effektivitetsgevinst, kan det samme objektet gjenbrukes. Dette er sjelden noe du trenger å bekymre deg for i applikasjonsdesign.
Egendefinerte klasser: definere __copy__ og __deepcopy__
Hvis standardkopieringen ikke er slik du ønsker for klassen din, kan du gi din egen kopieringslogikk. Hvis du definerer __copy__ og __deepcopy__, vil copy.copy og copy.deepcopy bruke 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- Ved å implementere
__copy__og__deepcopy__kan du på en fleksibel måte kontrollere klassespesifikk kopieringsatferd. For eksempel kan du håndtere tilfeller der enkelte attributter skal referere til (dele) det samme objektet, mens andre attributter skal dupliseres som helt nye objekter. - Argumentet
memosom sendes til__deepcopy__er en ordbok som registrerer objekter som allerede er behandlet under rekursiv kopiering og forhindrer uendelige løkker forårsaket av sirkulære referanser.
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 denne koden opprettes variabelen
shallow_copyvia en 'shallow copy', slik at bare det øverste nivået av objektet blir duplisert, og objektene det refererer til internt (i dette tilfellet listenchildren) deles med den opprinneligeroot_node. Som et resultat, når du legger til en ny node ishallow_copy, blir den deltechildren-listen oppdatert, og endringen gjenspeiles også i innholdet tilroot_node. - På den annen side opprettes variabelen
deep_copyvia en 'deep copy', slik at den interne strukturen dupliseres rekursivt, og det dannes et tre som er fullstendig uavhengig avroot_node. Derfor, selv om du legger til en ny node ideep_copy, påvirker det ikkeroot_node.
Sykliske referanser (rekursive objekter) og viktigheten av memo
Når en kompleks objektgraf refererer til seg selv (sykliske referanser), bruker deepcopy en memo-ordbok for å spore objekter som allerede er kopiert og forhindre uendelige løkker.
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 bruker
deepcopymemotil å referere til objekter som allerede er duplisert, noe som muliggjør trygg kopiering selv når det finnes sykluser. Du trenger en lignende mekanisme når du utfører rekursjon manuelt.
copyreg og tilpasset kopiering i serialiseringsstil (avansert)
Hvis du vil registrere spesialtilpasset kopieringsatferd i samspill med biblioteker, kan du bruke standardmodulen copyreg til å registrere hvordan objekter (re)konstrueres. Dette er nyttig for komplekse objekter og C-utvidelsestyper.
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)- Ved å bruke
copyregkan du angi rekonstrueringsregler som påvirker bådepickleogdeepcopy. Det er et avansert API, og i de fleste tilfeller holder det med__deepcopy__.
Praktiske forbehold og fallgruver
Det er flere viktige punkter for å sikre korrekt oppførsel når du bruker copy-modulen. Nedenfor forklarer vi vanlige fallgruver du kan møte i utviklingsarbeid, og hvordan du unngår dem.
- Ytelse
deepcopykan bruke mye minne og CPU, så vurder nøye om det er nødvendig. - Håndtering av elementer du vil dele
Hvis du vil at noen attributter, som store hurtigbuffere, skal forbli delte, unngå bevisst å kopiere referansene deres i
__deepcopy__. - Uforanderlig intern tilstand Hvis du holder uforanderlige data internt, kan kopiering være unødvendig.
- Tråder og eksterne ressurser Ressurser som ikke kan kopieres, som sockets og filhåndtak, er enten meningsløse å kopiere eller vil forårsake feil, så du må unngå å kopiere dem i designet.
Praktisk eksempel: et mønster for trygg oppdatering av ordbøker
Når et komplekst konfigurasjonsobjekt oppdateres, bruker dette eksempelet deepcopy for å beskytte de opprinnelige innstillingene.
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)Ved å bruke deepcopy kan du trygt lage avledede konfigurasjoner uten å risikere å skade standardinnstillingene. Dette er spesielt nyttig for nestede muterbare strukturer som konfigurasjoner.
Beste praksis
For å bruke copy-modulen trygt og effektivt, er det viktig å ha følgende praktiske retningslinjer i bakhodet.
- Velg mellom grunne og dype kopier basert på om endringer vil skje og deres omfang.
- Implementer
__copy__og__deepcopy__i egendefinerte klasser for å oppnå forventet oppførsel. - Fordi dype kopier er kostbare, reduser behovet for kopiering gjennom design der det er mulig. Vurder uforanderlighet og eksplisitte klonemetoder, blant andre teknikker.
- Når du håndterer sykliske referanser, bruk enten
deepcopyeller sørg for en memo-lignende mekanisme manuelt. - Design slik at eksterne ressurser som filhåndtak og tråder ikke kopieres.
Konklusjon
copy-modulen er standardverktøyet for objektduplisering i Python. Ved å forstå forskjellene mellom grunne og dype kopier riktig og implementere tilpasset kopieringsatferd ved behov, kan du utføre duplisering trygt og forutsigbart. Ved å avklare i designfasen om kopiering virkelig er nødvendig og hva som skal deles, kan du unngå unødvendige feil og ytelsesproblemer.
Du kan følge med på artikkelen ovenfor ved å bruke Visual Studio Code på vår YouTube-kanal. Vennligst sjekk ut YouTube-kanalen.