Поверхностное и глубокое копирование в Python
Перевод статьи Python Shallow Copy and Deep Copy.
Копирование объектов в Python
Оператор присваивания = не создаёт копию объекта. Присваивание создаёт новую переменную которая дублирует ссылку на исходный объект.
Рассмотрим пример
old_list = [[1, 2, 3], [4, 5, 6], [7, 8, 'a']]
new_list = old_list
new_list[2][2] = 9
print('Old List:', old_list)
print('ID of Old List:', id(old_list))
print('New List:', new_list)
print('ID of New List:', id(new_list))
После запуска программа выдаст следующее:
Old List: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
ID of Old List: 140673303268168
New List: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
ID of New List: 140673303268168
Обе переменные ссылаются на один объект, это подтверждает одинаковый идентификатор объекта 140673303268168. Так что при изменении значений в переменных new_list и old_list, изменение будет видно в обоих переменных.
Иногда нужно оставить исходное значение без изменений и модифицировать только новое. В Python, есть два способа сделать копию: поверхностное копирование и глубокое копирование.
Модуль Copy
Модуль copy используется для поверхностного и глубокого копирования. Например:
import copy
copy.copy(x)
copy.deepcopy(x)
Метод copy() возвращает поверхностную копию x, а метод deepcopy() возвращает глубокую копию x.
Поверхностное копирование
Поверхностное копирование создаёт новый объект и копирует ссылки на все вложенные объекты. Это означает что процесс копирования не рекурсивный и не создаёт копий вложенных объектов.
import copy
old_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
new_list = copy.copy(old_list)
print("Old list:", old_list)
print("New list:", new_list)
Вывод программы:
Old list: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
New list: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
В программе выше, мы создали вложенный список и его поверхностную копию методом copy(). Это означает что мы создали независимый объект с тем же содержимым.
Чтобы проверить что объекты разные добавим в один из списков новый элемент:
import copy
old_list = [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
new_list = copy.copy(old_list)
old_list.append([4, 4, 4])
print("Old list:", old_list)
print("New list:", new_list)
Вывод программы:
Old list: [[1, 1, 1], [2, 2, 2], [3, 3, 3], [4, 4, 4]]
New list: [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
Изменения в исходном списке (old_list) не повлияли на новый список (new_list).
Попробуем изменить один из вложенных объектов:
import copy
old_list = [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
new_list = copy.copy(old_list)
old_list[1][1] = 'AA'
print("Old list:", old_list)
print("New list:", new_list)
Вывод программы:
Old list: [[1, 1, 1], [2, 'AA', 2], [3, 3, 3]]
New list: [[1, 1, 1], [2, 'AA', 2], [3, 3, 3]]
Мы изменили вложенный в в оригинальный объект список (old_list[1]). Так как ссылки на вложенные элементы в двух списках одинаковые, изменение вложенного списка отразилось и на old_list и на new_list.
Глубокое копирование
Глубокое копирование создаёт новый объект и рекурсивно добавляет в него копии вложенных объектов из исходного объекта.
import copy
old_list = [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
new_list = copy.deepcopy(old_list)
print("Old list:", old_list)
print("New list:", new_list)
Вывод программы:
Old list: [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
New list: [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
В этом примере не видно отличий от использования функции copy(). Изменения видны если модифицировать вложенный список:
import copy
old_list = [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
new_list = copy.deepcopy(old_list)
old_list[1][0] = 'BB'
print("Old list:", old_list)
print("New list:", new_list)
Вывод программы:
Old list: [[1, 1, 1], ['BB', 2, 2], [3, 3, 3]]
New list: [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
Изменения видны только в списке old_list, так как список new_list содержит копии всех вложенных объектов.