Pythons `copy`-modul

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 memo som 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_copy via en 'shallow copy', slik at bare det øverste nivået av objektet blir duplisert, og objektene det refererer til internt (i dette tilfellet listen children) deles med den opprinnelige root_node. Som et resultat, når du legger til en ny node i shallow_copy, blir den delte children-listen oppdatert, og endringen gjenspeiles også i innholdet til root_node.
  • På den annen side opprettes variabelen deep_copy via en 'deep copy', slik at den interne strukturen dupliseres rekursivt, og det dannes et tre som er fullstendig uavhengig av root_node. Derfor, selv om du legger til en ny node i deep_copy, påvirker det ikke root_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 deepcopy memo til å 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 copyreg kan du angi rekonstrueringsregler som påvirker både pickle og deepcopy. 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 deepcopy kan 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 deepcopy eller 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.

YouTube Video