Практическая ситуация: после поверхностного копирования списка изменение вложенного списка отражается в оригинале. Какой механизм Python это объясняет?
Поверхностная копия создаёт новый внешний список, но не копирует вложенные изменяемые объекты. Поэтому исходный и скопированный список содержат ссылки на один и тот же вложенный список, и его изменение видно через обе структуры.
В Python переменные хранят ссылки на объекты, а не сами значения. Такой подход позволяет эффективно передавать и переиспользовать объекты без неявного копирования всей структуры данных.
Для коллекций это привело к разделению операций на поверхностное копирование, быстро создающее новый контейнер, и глубокое копирование, рекурсивно копирующее вложенные объекты. Выбор между ними нужен, чтобы явно управлять стоимостью копирования и совместным состоянием данных.
Если список содержит только неизменяемые значения, поверхностной копии обычно достаточно. Но если внутри находятся другие списки, словари или пользовательские изменяемые объекты, копирование только внешнего контейнера не изолирует вложенные данные.
Неверное предположение о полной независимости копии может привести к неожиданному изменению исходных данных: например, обработчик изменит рабочую копию, а состояние исходной записи тоже окажется изменено.
При поверхностном копировании создаётся новый объект внешнего списка, но ссылки на его элементы переносятся без копирования самих элементов. Для вложенного изменяемого объекта это означает общее состояние:
Методы copy(), срез списка и конструктор list(...) создают поверхностную копию. Для рекурсивного копирования применяется copy.deepcopy(...), но оно может быть значительно дороже и не всегда корректно или необходимо: пользовательские объекты могут иметь внешние ресурсы, циклические ссылки или собственную логику копирования.
Если нужна независимость только на одном известном уровне, часто лучше явно скопировать именно вложенную коллекцию. Это дешевле и понятнее, чем безусловно использовать глубокое копирование. Для неизменяемых объектов совместное использование безопаснее, поскольку их состояние нельзя изменить.
Сервис получает список заказов, где каждый заказ содержит изменяемый список позиций. Разработчик делает поверхностную копию списка заказов перед применением временного фильтра и удаляет позиции из вложенного списка. В результате меняются и исходные заказы, которые должны были оставаться неизменными.
Вариант с поверхностной копией быстрый и экономный по памяти, но сохраняет общие вложенные списки. deepcopy изолирует всю структуру, однако может копировать намного больше данных, чем требуется, и усложняет работу с объектами, связанными с ресурсами.
Оптимальное решение — скопировать внешний список и явно создать копии только тех вложенных коллекций, которые будут изменяться. Такой подход сохраняет независимость нужных данных, ограничивает затраты и делает границы владения состоянием очевидными.
Нет. После копирования операции добавления или удаления элементов внешнего списка выполняются независимо: original.append(...) не добавит элемент в копию. Общими остаются только сами элементы, ссылки на которые были перенесены, включая вложенные изменяемые объекты.
Нет. Глубокая копия нужна только тогда, когда необходимо рекурсивно отделить изменяемые вложенные объекты. Если структура содержит неизменяемые значения или часть данных должна быть общей, достаточно поверхностной либо выборочной копии. Кроме того, deepcopy может быть дорогим и иметь специальные ограничения для отдельных типов объектов.
Неизменяемый объект нельзя изменить на месте. Например, при операции над вложенной строкой создаётся новая строка, а ссылка в конкретном списке заменяется; общий исходный объект строки не меняется. Поэтому совместная ссылка на неизменяемый объект обычно безопасна, тогда как совместная ссылка на вложенный список или словарь создаёт риск общего изменяемого состояния.