Pythons `copy`-modul

Pythons `copy`-modul

Denne artikel forklarer Pythons copy-modul.

Med fokus på forskellene mellem overfladiske og dybe kopier giver vi en klar forklaring—fra de grundlæggende mekanismer for objektduplikering til anvendelser i brugerdefinerede klasser—sammen med praktiske eksempler.

YouTube Video

Pythons copy-modul

copy-modulet i Python er standardmodulet til håndtering af objektduplikering (kopiering). Målet er at forstå forskellen mellem overfladiske og dybe kopier og at kunne styre kopieringsadfærden for brugerdefinerede objekter.

Grundlæggende: Hvad er en overfladisk kopi?

Her illustrerer vi adfærden for overfladiske kopier for foranderlige objekter såsom lister og ordbøger. En overfladisk kopi duplikerer kun topniveau-objektet og deler referencerne indeni.

 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 kode bliver listen på øverste niveau duplikeret, men de indre underlister deles ved reference, så shallow[1].append(99) også afspejles i original. Dette er typisk opførsel for en overfladisk kopi.

Hvad er en dyb kopi?

En dyb kopi duplikerer objektet og alle dets interne referencer rekursivt og skaber et uafhængigt objekt. Brug den, når du vil duplikere komplekse, indlejrede strukturer sikkert.

 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 eksempel påvirker ændringer i deep ikke original. deepcopy kopierer hele objektgrafen og giver en uafhængig kopi.

Sådan vælger du, hvilken du skal bruge

Valget af kopi bør bestemmes af objektets struktur og dit formål. Hvis du ikke vil have, at ændringer i interne foranderlige elementer påvirker originalobjektet, er deepcopy passende. Dette skyldes, at den rekursivt duplikerer hele objektet og skaber en fuldstændig uafhængig kopi.

Omvendt, hvis det er tilstrækkeligt kun at duplikere topniveau-objektet, og du vægter hastighed og hukommelseseffektivitet, er copy (en overfladisk kopi) mere velegnet.

  • Du vil undgå, at originalen ændres, når du ændrer interne foranderlige elementer → brug deepcopy.
  • Det er nok kun at duplikere topniveauet, og du vil prioritere ydeevne (hastighed/hukommelse) → brug copy (overfladisk kopi).

Håndtering af indbyggede og uforanderlige objekter

Uforanderlige objekter (fx int, str og tupler hvis indhold er uforanderligt) behøver normalt ikke at blive kopieret. copy.copy kan returnere det samme objekt, når det er passende.

 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 kopiering af uforanderlige objekter giver ringe effektivitetsgevinst, kan det samme objekt genbruges. Dette er sjældent noget, du behøver bekymre dig om i applikationsdesign.

Brugerdefinerede klasser: definere __copy__ og __deepcopy__

Hvis standardkopieringen ikke er, hvad du forventer for din klasse, kan du levere din egen kopieringslogik. Hvis du definerer __copy__ og __deepcopy__, vil copy.copy og copy.deepcopy bruge 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 at implementere __copy__ og __deepcopy__ kan du fleksibelt styre klassespecifik kopieringsadfærd. Du kan for eksempel håndtere tilfælde, hvor visse attributter skal henvise til (dele) det samme objekt, mens andre attributter skal duplikeres som helt nye objekter.
  • Argumentet memo, der gives til __deepcopy__, er en ordbog, der registrerer objekter, som allerede er behandlet under rekursiv kopiering, og forhindrer uendelige løkker forårsaget af cirkulære referencer.
 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 kode oprettes variablen shallow_copy via en 'shallow copy', så kun objektets øverste niveau bliver duplikeret, og de objekter, det refererer til internt (i dette tilfælde listen children), deles med den oprindelige root_node. Som følge heraf, når du tilføjer en ny node til shallow_copy, opdateres den delte children-liste, og ændringen afspejles også i indholdet af root_node.
  • Omvendt oprettes variablen deep_copy via en 'deep copy', så den interne struktur duplikeres rekursivt, hvilket giver et træ, der er fuldstændigt uafhængigt af root_node. Derfor påvirker det ikke root_node, selv hvis du tilføjer en ny node til deep_copy.

Cykliske referencer (rekursive objekter) og betydningen af memo

Når en kompleks objektgraf refererer til sig selv (cykliske referencer), bruger deepcopy en memo-ordbog til at spore objekter, der allerede er kopieret, 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 bruger deepcopy memo til at henvise til objekter, der allerede er duplikeret, hvilket muliggør sikker kopiering selv i nærvær af cyklusser. Du har brug for en lignende mekanisme, når du udfører rekursion manuelt.

copyreg og serialiseringslignende brugerdefineret kopiering (avanceret)

Hvis du vil registrere særlig kopieringsadfærd i samspil med biblioteker, kan du bruge standardmodulet copyreg til at registrere, hvordan objekter (gen)konstrueres. Dette er nyttigt for komplekse objekter og C-udvidelsestyper.

 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 at bruge copyreg kan du angive rekonstrueringsregler, der påvirker både pickle og deepcopy. Det er et avanceret API, og i de fleste tilfælde er __deepcopy__ tilstrækkeligt.

Praktiske forbehold og faldgruber

Der er flere vigtige punkter for at sikre korrekt adfærd, når du bruger copy-modulet. Nedenfor forklarer vi almindelige faldgruber, du kan støde på i udvikling, og hvordan du undgår dem.

  • Ydeevne deepcopy kan bruge betydelige mængder hukommelse og CPU, så overvej nøje, om det er nødvendigt.
  • Håndtering af elementer, du ønsker at dele Hvis du vil have, at nogle attributter, såsom store caches, skal forblive delte, så undlad bevidst at kopiere deres referencer i __deepcopy__.
  • Uforanderlig intern tilstand Hvis du internt holder uforanderlige data, kan kopiering være unødvendig.
  • Tråde og eksterne ressourcer Ressourcer, der ikke kan kopieres, såsom sockets og filhåndtag, er enten meningsløse at kopiere eller vil forårsage fejl, så du skal undgå at kopiere dem gennem design.

Praktisk eksempel: et mønster til sikker opdatering af ordbøger

Ved opdatering af et komplekst konfigurationsobjekt bruger dette eksempel deepcopy for at beskytte de oprindelige indstillinger.

 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 hjælp af deepcopy kan du sikkert oprette afledte konfigurationer uden at risikere at beskadige standardindstillingerne. Dette er især nyttigt for indlejrede foranderlige strukturer såsom konfigurationer.

Bedste praksis

For at bruge modulet copy sikkert og effektivt er det vigtigt at have følgende praktiske retningslinjer i tankerne.

  • Vælg mellem overfladiske og dybe kopier baseret på, om der vil forekomme ændringer, og deres omfang.
  • Implementer __copy__ og __deepcopy__ i brugerdefinerede klasser for at opnå den forventede adfærd.
  • Fordi dybe kopier er dyre, bør du, når det er muligt, reducere behovet for kopiering gennem design. Overvej uforanderlighed og eksplicitte klonemetoder blandt andre teknikker.
  • Når du håndterer cykliske referencer, så udnyt enten deepcopy eller implementér en memo-lignende mekanisme manuelt.
  • Design således, at eksterne ressourcer som filhåndtag og tråde ikke kopieres.

Konklusion

copy-modulet er standardværktøjet til duplikering af objekter i Python. Ved korrekt at forstå forskellene mellem overfladiske og dybe kopier og implementere brugerdefineret kopieringsadfærd, når det er nødvendigt, kan du udføre duplikering sikkert og forudsigeligt. Ved at afklare allerede i designfasen, om kopiering virkelig er nødvendig, og hvad der bør deles, kan du undgå unødige fejl og ydeevneproblemer.

Du kan følge med i ovenstående artikel ved hjælp af Visual Studio Code på vores YouTube-kanal. Husk også at tjekke YouTube-kanalen.

YouTube Video