В КУРСЕ?

Разбираемся в теме

Список в Python изменился сразу в двух переменных: где спряталась общая ссылка?

Начинающий программист создаёт второй список, добавляет в него элемент и неожиданно обнаруживает изменение первого. Часто причина заключается в том, что второго списка вообще не появилось: два имени относятся к одному объекту. Разобраться помогает схема связей, в которой переменные и хранящиеся в памяти значения обозначены отдельно.

Присваивание создаёт связь с объектом

Пусть выполнено a = [10, 20], а затем b = a. После первой операции существует список из двух чисел, связанный с именем a. Вторая операция связывает с этим же списком имя b. Она не создаёт независимую копию содержимого. Теперь вызов b.append(30) изменяет общий список. Через a и через b будет видна последовательность [10, 20, 30]. Чтобы предсказать результат, нужно спросить не только о значении переменной, но и о том, к какому объекту она относится. На бумаге это один прямоугольник и две ведущие к нему стрелки.

Новая связь отличается от изменения списка

Продолжим тот же пример: после добавления числа выполним b = [99]. Теперь имя b связано с новым списком, а a по-прежнему относится к прежнему [10, 20, 30]. Эта операция не удаляет и не заменяет элементы старого списка через имя a. Различие заметно по действию. append меняет существующий список. Присваивание нового списка имени b меняет связь этого имени. Если описывать оба действия одним расплывчатым словом изменение, причины поведения программы становятся непонятными. Схема объектов показывает, какая именно часть состояния поменялась.

Поверхностная копия отделяет внешний список

Для нового независимого внешнего списка можно использовать b = a.copy(). Если a содержит обычные числа, добавление нового элемента к b не добавит его к a. У двух внешних списков теперь разные объекты, хотя первоначально их элементы совпадают. Однако список может содержать другие списки. Пусть a = [[1], [2]], затем b = a.copy(). Внешние контейнеры разные, но вложенные списки остаются общими. Поэтому b[0].append(9) изменит первый вложенный список, видимый и через a. Зато добавление отдельного элемента во внешний b не увеличит длину внешнего a. Глубокое копирование решает другую задачу: создаёт копии вложенных объектов по правилам соответствующего механизма. Применять его автоматически ко всем данным не стоит. Сначала определяют, какие части должны быть независимыми, а какие намеренно остаются общими.

Попробуйте на практике

Предскажите поведение списков по схеме, прежде чем проверять выражения в учебном интерпретаторе.

  1. Для a = [10, 20] и b = a нарисуйте один список с двумя стрелками. Запишите результат после b.append(30).
  2. От того же состояния выполните на схеме b = [99]. Перерисуйте только связь имени b и подпишите содержимое обоих списков.
  3. Начните новый пример с a = [10, 20] и b = a.copy(). Добавьте 30 через b и сравните результат с первым шагом.
  4. В отдельном примере создайте a = [[1], [2]] и b = a.copy(). Покажите два внешних списка и общие вложенные объекты, затем предскажите действие b[0].append(9).

Как проверить результат. В первом случае оба имени показывают [10, 20, 30]. После нового присваивания b показывает [99]. При копировании внешнего списка добавление числа затрагивает только b, а изменение общего вложенного списка видно через оба имени.

Частые вопросы

Если значения списков равны, это один объект?

Не обязательно. Равенство содержимого и тождественность объекта являются разными проверками. Два независимо созданных списка могут содержать одинаковые элементы.

Почему ошибка появляется только при вложенных данных?

Поверхностная копия создаёт новый внешний контейнер, сохраняя ссылки на его элементы. Пока изменяется только внешний список, общность вложенных объектов может оставаться незаметной.

Самостоятельный разбор темы. Содержание конкретной обучающей программы здесь не представлено.

Зарегистрируйтесь, чтобы уточнить возможность доступа к этому материалу

Зарегистрироваться
← К списку материалов