Przejdź do treści
> 💻 🧠 Kod 1001 > 📑 Ściągawki > 🐍 Ściągawki Python > Kopiowanie obiektów w Pythonie

Kopiowanie obiektów w Pythonie

🔎 Dlaczego kopiowanie obiektów jest w ogóle ważne?

W Pythonie zmienne to nie „pudełka” z danymi, ale raczej „etykiety” lub referencje (odwołania) wskazujące na obiekty w pamięci. Kiedy wykonujesz proste przypisanie, takie jak to:

lista_a = [1, 2, 3]
lista_b = lista_a

Nie tworzysz nowej listy. Tworzysz po prostu kolejną referencję (lista_b), która wskazuje na dokładnie ten sam obiekt listy.

👉 Każda zmiana w lista_a zostanie natychmiast odzwierciedlona w lista_b (i na odwrót), ponieważ jest to ten sam obiekt.

Analogia: Wyobraź sobie, że masz dokument w Google Docs. Jeśli wyślesz znajomemu link do niego, będzie on edytował Twój oryginalny dokument. To jest właśnie przypisanie. Jeśli jednak wybierzesz „Plik” → „Utwórz kopię”, powstanie całkowicie niezależny dokument. Moduł copy w Pythonie istnieje właśnie po to, aby tworzyć tego rodzaju niezależne kopie.

⚙️ Dwa rodzaje kopiowania

Moduł copy dostarcza dwie kluczowe funkcje:

  • copy.copy() — tworzy kopię płytką (shallow copy).
  • copy.deepcopy() — tworzy kopię głęboką (deep copy).

Główna różnica między nimi polega na tym, jak traktują obiekty zagnieżdżone (takie jak listy wewnątrz list lub słowniki wewnątrz słowników).


🟦 Kopia płytka (copy.copy())

copy.copy() tworzy nowy obiekt-kontener na najwyższym poziomie, ale zamiast kopiować obiekty zagnieżdżone, wstawia do niego referencje do oryginalnych obiektów.

import copy

lista_oryginalna = [1, 2, [3, 4]]
kopia_plytka = copy.copy(lista_oryginalna)

# Zmodyfikuj zagnieżdżoną listę w oryginale
lista_oryginalna[2][0] = 99

print(f"Oryginał: {lista_oryginalna}")    # Wydruk: Oryginał: [1, 2, [99, 4]]
print(f"Kopia płytka: {kopia_plytka}") # Wydruk: Kopia płytka: [1, 2, [99, 4]]

Jak widać, modyfikacja zagnieżdżonej listy wpłynęła zarówno na oryginał, jak i na kopię, ponieważ współdzielą one tę zagnieżdżoną listę.

Diagram ASCII:

lista_oryginalna → [ 1, 2, ───► lista_wewnetrzna ]
kopia_plytka     → [ 1, 2, ───► lista_wewnetrzna ]
                               lista_wewnetrzna   → [ 99, 4 ]

🟩 Kopia głęboka (copy.deepcopy())

copy.deepcopy() rozwiązuje problem współdzielonych referencji. Ta funkcja rekurencyjnie przechodzi przez cały obiekt i tworzy pełne, niezależne kopie wszystkiego, co napotka, włączając w to wszystkie obiekty zagnieżdżone.

import copy

lista_oryginalna = [1, 2, [3, 4]]
kopia_gleboka = copy.deepcopy(lista_oryginalna)

# Zmodyfikuj zagnieżdżoną listę w oryginale
lista_oryginalna[2][0] = 99

print(f"Oryginał: {lista_oryginalna}") # Wydruk: Oryginał: [1, 2, [99, 4]]
print(f"Kopia głęboka: {kopia_gleboka}")     # Wydruk: Kopia głęboka: [1, 2, [3, 4]]```

Teraz `kopia_gleboka` jest całkowicie niezależna.

**Diagram ASCII:**

lista_oryginalna → [ 1, 2, ───► lista_a ]
kopia_gleboka → [ 1, 2, ───► lista_b ]
lista_a → [ 99, 4 ]
lista_b → [ 3, 4 ]

---

#### **📑 Ściągawka: `copy.copy()` vs `copy.deepcopy()`**

| Typ obiektu | Przykład | `copy.copy()` (płytka) | `copy.deepcopy()` (głęboka) |
| :--- | :--- | :--- | :--- |
| **list** (lista) | `[1, 2, [3, 4]]` | Nowa lista, ale zagnieżdżona lista `[3, 4]` jest **współdzielona** | Całkowicie niezależna kopia; zagnieżdżona lista również jest kopiowana |
| **dict** (słownik) | `{"a": 1, "b": {"c": 2}}` | Nowy słownik, ale zagnieżdżony `{"c": 2}` jest **współdzielony** | Niezależny słownik i wszystkie zagnieżdżone słowniki |
| **set** (zbiór) | `{1, 2, (3, 4)}` | Nowy zbiór; zagnieżdżone elementy modyfikowalne byłyby **współdzielone** | Nowy zbiór; wszystkie zagnieżdżone elementy są kopiowane rekurencyjnie |
| **tuple** (krotka) | `(1, 2, [3, 4])` | Zwraca tę samą krotkę, ale zagnieżdżona lista jest **współdzielona** | Nowa krotka z **kopią** zagnieżdżonej listy |
| **str, int, float, bool** | `"cześć"`, `42`, `3.14`, `True` | Zwraca ten sam obiekt (są niemutowalne, nie ma potrzeby kopiować) | To samo co kopia płytka |
| **klasa niestandardowa** | `Wezel(1, Wezel(2))` | Nowy obiekt na najwyższym poziomie; obiekty zagnieżdżone są **współdzielone** | Całkowicie niezależna kopia obiektu i wszystkich zagnieżdżonych obiektów |
| **obiekt z `__slots__`** | Klasa z `__slots__` | Kopiowany jest tylko obiekt najwyższego poziomu | Kopiowane są wszystkie zagnieżdżone obiekty, jeśli wspierają kopiowanie |
| **plik / gniazdo / strumień**| `open("file.txt")` | **Błąd** (obiekt niekopiowalny) | **Błąd** (obiekt niekopiowalny) |

---

#### **🧪 Przykłady na żywo do tabeli**

📌 **list**

python
import copy
a = [1, 2, [3, 4]]
b = copy.copy(a)
c = copy.deepcopy(a)
a[2][0] = 99
print(f”copy: {b}”) # [1, 2, [99, 4]]
print(f”deepcopy: {c}”) # [1, 2, [3, 4]]

📌 **dict**

python
import copy
a = {„a”: 1, „b”: {„c”: 2}}
b = copy.copy(a)
c = copy.deepcopy(a)
a[„b”][„c”] = 42
print(f”copy: {b}”) # {’a’: 1, 'b’: {’c’: 42}}
print(f”deepcopy: {c}”) # {’a’: 1, 'b’: {’c’: 2}}

📌 **set**

python
import copy
a = {1, 2, (3, 4)} # Zbiory mogą zawierać tylko typy niemutowalne
b = copy.copy(a)
c = copy.deepcopy(a)
print(b == c) # True (ale to są dwa niezależne obiekty w pamięci)

📌 **tuple**

python
import copy
a = (1, 2, [3, 4]) # Krotka jest niemutowalna, ale jej element-lista już nie
b = copy.copy(a)
c = copy.deepcopy(a)
a[2][0] = 99
print(f”copy: {b}”) # (1, 2, [99, 4])
print(f”deepcopy: {c}”) # (1, 2, [3, 4])

📌 **Klasa niestandardowa**

python
import copy
class Wezel:
def init(self, wartosc, dziecko=None):
self.wartosc = wartosc
self.dziecko = dziecko

a = Wezel(1, Wezel(2))
b = copy.copy(a)
c = copy.deepcopy(a)
a.dziecko.wartosc = 99
print(f”copy: {b.dziecko.wartosc}”) # 99
print(f”deepcopy: {c.dziecko.wartosc}”) # 2

---

#### **📌 Dostosowywanie zachowania: `__copy__` i `__deepcopy__`**

Możesz kontrolować sposób kopiowania własnych obiektów, definiując w ich klasie specjalne metody.

python
import copy
class Niestandardowa:
def init(self, x):
self.x = x
def copy(self):
print(„Wywołano copy!”)
# Zwróć nową instancję, ale z tą samą referencją do danych wewnętrznych
return Niestandardowa(self.x)
def deepcopy(self, memo):
# memo to słownik śledzący już skopiowane obiekty (zapobiega rekurencji)
print(„Wywołano deepcopy!”)
# Stwórz całkowicie nową kopię, włączając dane zagnieżdżone
return Niestandardowa(copy.deepcopy(self.x, memo))

obj = Niestandardowa([1, 2])
c1 = copy.copy(obj) # Wydruk: Wywołano copy!
c2 = copy.deepcopy(obj) # Wydruk: Wywołano deepcopy!
„`


⚡ Wydajność i pułapki

  • Szybkość: copy.copy() jest znacznie szybsze, ponieważ nie przechodzi rekurencyjnie przez całą strukturę danych. copy.deepcopy() może być bardzo wolne dla dużych i głęboko zagnieżdżonych obiektów.
  • Obiekty niekopiowalne: Zasoby systemowe, takie jak otwarte pliki, gniazda (sockets) czy strumienie, nie mogą być kopiowane. Próba zrobienia tego spowoduje błąd.
  • Referencje cykliczne: deepcopy jest wystarczająco inteligentne, aby poradzić sobie z referencjami cyklicznymi (gdy obiekt A odnosi się do B, a B z powrotem do A) bez wpadania w nieskończoną pętlę.

Kiedy czego używać: Główne zasady

  1. Używaj kopii płytkiej (copy.copy()), jeśli Twój obiekt zawiera tylko dane niemutowalne lub jeśli celowo chcesz, aby kopia i oryginał współdzieliły zagnieżdżone obiekty. Jest to szybkie i wydajne.
  2. Używaj kopii głębokiej (copy.deepcopy()), gdy potrzebujesz pełnej, 100% niezależności kopii od oryginału, zwłaszcza podczas pracy ze złożonymi strukturami danych (listami, słownikami, instancjami klas).
  3. Dla obiektów niemutowalnych (liczby, ciągi znaków, krotki bez modyfikowalnych elementów) kopiowanie jest generalnie bezcelowe — Python i tak optymalizuje ich użycie.
  4. Pamiętaj, że możesz dostosować proces za pomocą __copy__ i __deepcopy__ dla pełnej kontroli.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Wymagane pola są oznaczone *