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_copyvia en 'shallow copy', så kun objektets øverste niveau bliver duplikeret, og de objekter, det refererer til internt (i dette tilfælde listenchildren), deles med den oprindeligeroot_node. Som følge heraf, når du tilføjer en ny node tilshallow_copy, opdateres den deltechildren-liste, og ændringen afspejles også i indholdet afroot_node. - Omvendt oprettes variablen
deep_copyvia en 'deep copy', så den interne struktur duplikeres rekursivt, hvilket giver et træ, der er fuldstændigt uafhængigt afroot_node. Derfor påvirker det ikkeroot_node, selv hvis du tilføjer en ny node tildeep_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
deepcopymemotil 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
copyregkan du angive rekonstrueringsregler, der påvirker bådepickleogdeepcopy. 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
deepcopykan 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
deepcopyeller 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.