Sytuacja kobiet w IT w 2024 roku
6.07.20203 min
Prashant Sharma

Prashant SharmaSoftware Engineer

Różnica między operatorem przypisania, płytkim oraz głębokim kopiowaniem w Pythonie

Poznaj różnicę między trzema sposobami tworzenia kopii w Pythonie: operator przypisania, płytkie i głębokie kopiowanie.

Różnica między operatorem przypisania, płytkim oraz głębokim kopiowaniem w Pythonie

Będziemy tutaj omawiać temat kopiowania w Pythonie. Wyróżnimy 3 sposoby na kopiowanie i dowiemy się, co robi i czym się różni każda operacja.


  1. Operator przypisania (=)
  2. Płytkie kopiowanie
  3. Głębokie kopiowanie

Operator przypisania (=)

>>> a = [1, 2, 3, 4, 5]
>>> b = a


W powyższym przykładzie operator przypisania nie tworzy kopii obiektów Pythona. Zamiast tego kopiuje adres pamięci (lub wskaźnik) z a na b, (b = a), co oznacza, że zarówno a, jak i b wskazują na ten sam adres pamięci. Możemy tutaj użyć metody id(), aby uzyskać adres obiektu w pamięci i sprawdzić, czy obie listy wskazują tę samą pamięć.

>>> id(a) == id(b)
True
>>> print('id of a - {}, id of b - {}'.format(id(a), id(b)))
id of a - 140665942562048, id of b - 140665942562048


Jeśli więc chcesz edytować nową listę, zostanie ona również zaktualizowana na oryginalnej liście:

>>> b.append(6) 
>>> a
[1, 2, 3, 4, 5, 6]
>>> b
[1, 2, 3, 4, 5, 6]


Dzieje się tak, ponieważ w pamięci jest tylko jedna instancja tej listy.

Płytkie kopiowanie

Płytkie kopiowanie tworzy nowy obiekt złożony, a następnie (w możliwym zakresie) wstawia do niego referencje do obiektów znalezionych w oryginale. Mamy trzy różne sposoby na płytkie kopiowanie:

nums = [1, 2, 3, 4, 5]      
>>> import copy
>>> m1 = copy.copy(nums)  # make a shallow copy by using copy module
>>> m2 = list(nums)       # make a shallow copy by using the factory function
>>> m3 = nums[:]          # make a shallow copy by using the slice operator


Wszystkie powyższe listy zawierają tutaj te same wartości, co oryginalna lista:

>>> print(nums == m1 == m2 == m3)
True

Różnią się jednak między sobą adresem pamięci.

>>> print('nums_id - {}, m1_id - {}, m2_id - {}, m3_id = {}'.format(id(nums), id(m1), id(m2), id(m3)))
nums_id - 140665942650624, m1_id - 140665942758976, m2_id - 140665942759056, m3_id = 140665942692000


Oznacza to, że tym razem obiekt każdej listy ma swój własny, niezależny adres pamięci. Teraz przechodzimy do bardziej interesującej części — jeśli oryginalna lista jest obiektem złożonym (np. listą innych list), to po płytkim kopiowaniu nowe elementy nadal odwołują się do oryginalnych elementów listy.

Jeśli więc zmodyfikujesz elementy zmienne (np. listy), zmiany zostaną odzwierciedlone w oryginalnych elementach. Spójrzmy na poniższy przykład, aby lepiej to zrozumieć:

>>> import copy
>>> a = [[1, 2], [3, 4]]            
>>> b = copy.copy(a)
>>> id(a) == id(b)
False
>>> b[0].append(5)

>>> b
[[1, 2, 5], [3, 4]]
>>> a
[[1, 2, 5], [3, 4]]      # changes reflected in original list also


Jak widać w powyższym przykładzie, gdy modyfikujemy wewnętrzne elementy listy na nowej liście, to są one również aktualizowana na oryginalnej liście, ponieważ a[0] i b[0] nadal wskazują na ten sam adres pamięci (oryginalnej listy).

>>> print('a[0] - {} , b[0] - {}'.format(id(a[0]), id(b[0])))       
a[0] - 140399422977280 , b[0] - 140399422977280
>>> id(a[0]) == id(b[0])
True
>>> id(a[1]) == id(b[1])
True

Nowa lista b ma więc własny adres pamięci, ale jej elementy już nie. Dzieje się tak, ponieważ przy płytkim kopiowaniu, zamiast powielania elementów listy do nowego obiektu, kopiowane są tylko referencje do nich. Dlatego podczas wprowadzania zmian w oryginalnym obiekcie jest on odzwierciedlany w kopiowanych obiektach i odwrotnie.

Głębokie kopiowanie

Głębokie kopiowanie tworzy nowy obiekt złożony, a następnie rekurencyjnie wstawia do niego kopie obiektów znalezionych w oryginale. Tworzenie głębokiej kopii jest wolniejsze, ponieważ wykonujesz nowe kopie dla całej zawartości.

W ten sposób, zamiast powielania adresu złożonych obiektów, tworzymy pełną kopię wszystkich elementów (prostych i złożonych) oryginalnej listy i przydzielamy inny adres pamięci dla nowej listy. Następnie przypisujemy im skopiowane elementy. Aby wykonać głębokie kopiowanie, musimy zaimportować moduł copy i użyć copy.deepcopy().

>>> import copy
>>> a = [[1, 2, 3], [4, 5, 6]]  
>>> b = copy.deepcopy(a)                     
>>> id(a) == id(b)
False
>>> id(a[0]) == id(b[0])      # memory address is different      
False
>>> a[0].append(8)            # modify the list                                                                                                                                            
>>> a
[[1, 2, 3, 8], [4, 5, 6]]
>>> b
[[1, 2, 3], [4, 5, 6]]        # New list's elements didn't get affected


Jak widać powyżej, nie ma to wpływu na oryginalną listę.

Podsumowanie

Różnica między płytkim a głębokim kopiowaniem dotyczy tylko obiektów złożonych (obiektów zawierających inne obiekty, takie jak listy lub instancje klas).

Źródło


Oryginał tekstu w języku angielskim możesz przeczytać tutaj

<p>Loading...</p>