Moduł `copy` Pythona
Ten artykuł wyjaśnia moduł copy Pythona.
Koncentrując się na różnicach między płytkimi a głębokimi kopiami, przedstawiamy jasne wyjaśnienie — od podstawowych mechanizmów duplikowania obiektów po zastosowania w klasach niestandardowych — wraz z praktycznymi przykładami.
YouTube Video
Moduł copy Pythona
Moduł copy w Pythonie jest standardowym modułem do obsługi duplikowania (kopiowania) obiektów. Celem jest zrozumienie różnicy między kopiami płytkimi i głębokimi oraz możliwość kontrolowania zachowania kopiowania dla obiektów niestandardowych.
Podstawy: czym jest płytka kopia?
Tutaj ilustrujemy zachowanie płytkich kopii dla obiektów mutowalnych, takich jak listy i słowniki. Płytka kopia duplikuje tylko obiekt najwyższego poziomu i współdzieli wewnętrzne referencje.
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)W tym kodzie lista najwyższego poziomu jest zduplikowana, ale wewnętrzne podlisty są współdzielone przez odwołanie, więc shallow[1].append(99) znajduje odzwierciedlenie także w original. To typowe zachowanie płytkiej kopii.
Czym jest głęboka kopia?
Głęboka kopia rekurencyjnie duplikuje obiekt oraz wszystkie jego wewnętrzne referencje, tworząc niezależny obiekt. Używaj jej, gdy chcesz bezpiecznie zduplikować złożone, zagnieżdżone struktury.
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)W tym przykładzie zmiany w deep nie wpływają na original. deepcopy kopiuje cały graf obiektów, tworząc niezależną replikę.
Jak zdecydować, której użyć
Wybór rodzaju kopii powinien wynikać ze struktury obiektu i celu, jaki chcesz osiągnąć. Jeśli nie chcesz, aby zmiany wewnętrznych elementów mutowalnych wpływały na obiekt oryginalny, właściwe będzie użycie deepcopy. Dzieje się tak, ponieważ rekurencyjnie duplikuje cały obiekt, tworząc całkowicie niezależną kopię.
Z drugiej strony, jeśli wystarczy zduplikować jedynie obiekt najwyższego poziomu i zależy ci na szybkości oraz oszczędności pamięci, bardziej odpowiednia będzie copy (kopia płytka).
- Chcesz uniknąć zmian w oryginale podczas modyfikacji wewnętrznych elementów mutowalnych → użyj
deepcopy. - Wystarczy duplikacja tylko najwyższego poziomu i chcesz priorytetowo traktować wydajność (szybkość/pamięć) → użyj
copy(kopia płytka).
Obsługa obiektów wbudowanych i niemutowalnych
Obiekty niemutowalne (np. int, str oraz krotki o niemutowalnej zawartości) zazwyczaj nie wymagają kopiowania. copy.copy może, gdy to zasadne, zwrócić ten sam obiekt.
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)Ponieważ kopiowanie obiektów niemutowalnych daje niewielkie korzyści wydajnościowe, ten sam obiekt może być ponownie użyty. Rzadko jest to coś, czym trzeba się przejmować przy projektowaniu aplikacji.
Klasy niestandardowe: definiowanie __copy__ i __deepcopy__
Jeśli domyślne kopiowanie nie odpowiada potrzebom twojej klasy, możesz dostarczyć własną logikę kopiowania. Jeśli zdefiniujesz __copy__ i __deepcopy__, copy.copy i copy.deepcopy będą z nich korzystać.
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- Implementując
__copy__i__deepcopy__, możesz elastycznie kontrolować specyficzne dla klasy zachowanie kopiowania. Na przykład możesz obsłużyć przypadki, w których niektóre atrybuty powinny odwoływać się do (współdzielić) tego samego obiektu, podczas gdy inne atrybuty powinny być duplikowane jako zupełnie nowe obiekty. - Argument
memoprzekazywany do__deepcopy__to słownik, który zapisuje obiekty już przetworzone podczas rekurencyjnego kopiowania i zapobiega nieskończonym pętlom spowodowanym przez cykliczne referencje.
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)- W tym kodzie zmienna
shallow_copyjest utworzona poprzez 'płytką kopię', więc duplikowany jest tylko najwyższy poziom obiektu, a obiekty, do których odwołuje się on wewnętrznie (w tym przypadku listachildren), są współdzielone z oryginalnymroot_node. W rezultacie, gdy dodasz nowy węzeł doshallow_copy, współdzielona listachildrenzostaje zaktualizowana i zmiana jest również odzwierciedlona w zawartościroot_node. - Z kolei zmienna
deep_copyjest tworzona poprzez 'głęboką kopię', więc wewnętrzna struktura jest rekurencyjnie duplikowana, tworząc drzewo całkowicie niezależne odroot_node. Dlatego nawet jeśli dodasz nowy węzeł dodeep_copy, nie wpływa to naroot_node.
Cykliczne referencje (obiekty rekurencyjne) i znaczenie memo
Gdy złożony graf obiektów odwołuje się do siebie (cykliczne referencje), deepcopy używa słownika memo do śledzenia już skopiowanych obiektów i zapobiegania nieskończonym pętlom.
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))- Wewnątrz
deepcopyużywamemodo odwoływania się do już zduplikowanych obiektów, co umożliwia bezpieczne kopiowanie nawet w obecności cykli. Podczas ręcznego wykonywania rekurencji potrzebujesz podobnego mechanizmu.
copyreg i niestandardowe kopiowanie w stylu serializacji (zaawansowane)
Jeśli chcesz zarejestrować specjalne zachowanie kopiowania we współpracy z bibliotekami, możesz użyć standardowego modułu copyreg, aby zarejestrować sposób (re)konstrukcji obiektów. Jest to przydatne dla złożonych obiektów i typów rozszerzeń w C.
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)- Za pomocą
copyregmożesz dostarczyć reguły rekonstrukcji, które wpływają zarówno napickle, jak i nadeepcopy. To zaawansowane API i w większości przypadków__deepcopy__jest wystarczające.
Praktyczne uwagi i pułapki
Istnieje kilka ważnych kwestii, aby zapewnić poprawne zachowanie podczas używania modułu copy. Poniżej wyjaśniamy typowe pułapki, na które możesz natrafić podczas tworzenia oprogramowania, oraz jak ich unikać.
- Wydajność
deepcopymoże pochłaniać dużo pamięci i czasu CPU, dlatego dokładnie rozważ, czy jest konieczne. - Obsługa elementów, które chcesz współdzielić
Jeśli chcesz, aby niektóre atrybuty, takie jak duże pamięci podręczne, pozostały współdzielone, celowo unikaj kopiowania ich referencji w
__deepcopy__. - Niemutowalny stan wewnętrzny Jeśli wewnętrznie przechowujesz dane niemutowalne, kopiowanie może być zbędne.
- Wątki i zasoby zewnętrzne Zasoby, których nie można kopiować, takie jak gniazda sieciowe i uchwyty plików, są albo bezcelowe do kopiowania, albo spowodują błędy, dlatego należy z założenia unikać ich kopiowania.
Praktyczny przykład: wzorzec bezpiecznej aktualizacji słowników
Podczas aktualizowania złożonego obiektu konfiguracji ten przykład używa deepcopy, aby chronić oryginalne ustawienia.
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)Dzięki deepcopy możesz bezpiecznie tworzyć konfiguracje pochodne bez ryzyka uszkodzenia ustawień domyślnych. Jest to szczególnie przydatne w przypadku zagnieżdżonych, mutowalnych struktur, takich jak konfiguracje.
Najlepsze praktyki
Aby bezpiecznie i skutecznie korzystać z modułu copy, ważne jest, aby pamiętać o następujących praktycznych wskazówkach.
- Wybieraj między kopiami płytkimi a głębokimi w zależności od tego, czy zajdą zmiany i jaki będzie ich zakres.
- Zaimplementuj
__copy__i__deepcopy__w klasach niestandardowych, aby uzyskać oczekiwane zachowanie. - Ponieważ kopie głębokie są kosztowne, kiedy to możliwe ograniczaj potrzebę kopiowania już na etapie projektu. Rozważ m.in. niemutowalność i jawne metody klonowania.
- Przy cyklicznych referencjach skorzystaj z
deepcopylub ręcznie zapewnij mechanizm podobny domemo. - Projektuj w taki sposób, aby zewnętrzne zasoby, takie jak uchwyty plików i wątki, nie były kopiowane.
Wnioski
Moduł copy jest standardowym narzędziem do duplikowania obiektów w Pythonie. Dzięki poprawnemu zrozumieniu różnic między kopiami płytkimi i głębokimi oraz wdrożeniu niestandardowego zachowania kopiowania w razie potrzeby możesz duplikować obiekty bezpiecznie i przewidywalnie. Precyzując już na etapie projektowania, czy kopiowanie jest rzeczywiście konieczne i co powinno być współdzielone, możesz uniknąć zbędnych błędów i problemów z wydajnością.
Możesz śledzić ten artykuł, korzystając z Visual Studio Code na naszym kanale YouTube. Proszę również sprawdzić nasz kanał YouTube.