ПрограммированиеBackend Python разработчик

В чем разница между shallow copy и простым присваиванием переменной? Как работает copy.copy() применительно к вложенным структурам данных (списки в списках)?

Проходите собеседования с ИИ помощником Hintsage

Ответ

Присваивание переменной (например, a = b) в Python не копирует сам объект, а лишь создает новое имя (ссылку) на существующий объект. Изменения по одному имени будут видны и по другому, если это изменяемый объект.

Shallow copy (поверхностное копирование), например через copy.copy(obj) или срез [:] для списка, создает новый объект верхнего уровня, но вложенные объекты внутри копируются по ссылке (т.е. обе структуры "смотрят" на одни и те же подконтейнеры). Если изменять вложенные объекты, изменения будут видны через оба объекта.

Пример:

import copy lst1 = [[1,2], [3,4]] lst2 = copy.copy(lst1) # или lst1[:] lst1[0][0] = 100 print(lst2) # [[100, 2], [3, 4]]

lst2 — новый список, но его первый элемент — тот же вложенный список.

Вопрос с подвохом

Вопрос: Чем отличается lst2 = lst1[:] и lst2 = copy.copy(lst1)?

Ответ: На практике для обычных (одноуровневых) списков нет отличий — оба способа делают поверхностную копию списка. Однако, для пользовательских контейнерных классов может быть разное поведение (например, если реализован свой метод __copy__). Точно так же для других типов (dict, set и др.) использовать специализированный модуль copy безопаснее.

import copy lst1 = [1, 2, 3] lst2 = lst1[:] lst3 = copy.copy(lst1) print(lst2 == lst3) # True

Примеры реальных ошибок из-за незнания тонкостей темы


История

В проекте с обработкой конфигов разработчик дублировал дефолтные параметры через присваивание params = default_params и рассчитывал "изолированно" их менять. В итоге любые изменения в любой копии вели к каскаду изменений во всех частях приложения, потому что на деле работали с одним объектом.


История

Неопытный программист использовал поверхностную копию списка для хранения состояния игры (game_states = states[:]). При вложенной структуре (списки фигур на поле) изменения внутри одного состояния "протекали" в другие, ломая историю откатов и повторов ходов.


История

При попытке клонирования данных в ООП-приложении выбор был между срезом и copy.copy(). Но в структуре встретился свой класс со своим методом copy, который был учтен только при использовании copy.copy(). Срез проигнорировал логику копирования объекта, и возникли неочевидные баги.