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.
- Operator przypisania
(=)
- Płytkie kopiowanie
- 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.