Два имени могут вести к одному объекту
Создадим список командой a = [3, 6]. Затем выполним b = a. Теперь оба имени относятся к одному списку. Если записать b[0] = 9, меняется его первый элемент; индекс первого элемента равен нулю. При просмотре a и b мы увидим одинаковое содержимое: [9, 6]. Важно объяснить результат точно: программа не нашла второй список и не скопировала в него изменение. Второго списка на этом шаге вообще не создавали. На бумаге это удобно изобразить одним прямоугольником с содержимым и двумя стрелками от имён к нему.
Копия создаёт отдельный внешний список
Продолжим тот же пример командой c = a.copy(). Теперь появляется новый список с теми же элементами. Если выполнить c[1] = 8, через c будет видно [9, 8], а через a останется [9, 6]. Мы заменили второй элемент только в новом внешнем списке. На схеме нужен ещё один прямоугольник и отдельная стрелка от имени c. Такой рисунок помогает не путать совпадение содержимого с существованием одного и того же объекта. Два списка могут содержать одинаковые числа, но оставаться разными списками.
Вложенность меняет смысл независимости
Поверхностная копия не копирует рекурсивно всё содержимое. Рассмотрим отдельный пример: u = [[3], [6]], затем v = u.copy(). Внешних списков теперь два, но ссылки на внутренние списки у них общие. После v[0].append(9) первый внутренний список содержит три и девять. Через оба внешних имени будет видно [[3, 9], [6]]. Здесь мы не заменяли элемент внешнего списка, а изменили общий внутренний объект. Поэтому перед выбором способа копирования нужно понять, какие части структуры предполагается менять и какие связи должны сохраниться. Названия переменных сами по себе этого не определяют.
Проверять действие, а не только последний вывод
При разборе ошибки полезно двигаться по одной операции. После присваивания спросите, появился ли новый объект. После изменения уточните, какой объект затронут: внешний список или вложенный элемент. В нашем первом примере замена числа в c не меняет a, а во втором добавление числа во внутренний список видно и через u, и через v. Это не противоречие: действия направлены на разные уровни структуры. Поверхностная копия подходит не для всех задач, но и более глубокое копирование не следует выбирать автоматически. Сначала определите, какие данные должны быть независимыми по смыслу программы.
Попробуйте на практике
Предскажите результаты трёх коротких опытов, затем проверьте их в интерпретаторе Python.
- Выполните a = [3, 6], затем b = a и b[0] = 9. Перед просмотром значений нарисуйте, сколько списков было создано.
- Выполните c = a.copy(), затем c[1] = 8. Запишите ожидаемое содержимое a и c и сравните с фактическим.
- Начните отдельный пример: u = [[3], [6]], затем v = u.copy() и v[0].append(9). Отметьте на схеме общие внутренние списки.
- Сопоставьте все результаты и для каждого изменения назовите объект, на который была направлена операция.
Как проверить результат. После первого опыта a и b содержат [9, 6] и обозначают один список. Во втором a остаётся [9, 6], а c содержит [9, 8]. В третьем u и v показывают [[3, 9], [6]]: их внешние списки различны, первый внутренний список общий.
Частые вопросы
Одинаковое содержимое означает один объект?
Нет. Совпадение элементов и тождество объекта — разные отношения. В рисунке два прямоугольника могут содержать одинаковые числа, но представлять разные списки.
Метод copy делает бесполезную копию, если есть вложенность?
Нет. Он создаёт отдельный внешний список, сохраняя ссылки на элементы. Это осмысленное поведение; важно учитывать его при изменении вложенных объектов.
Самостоятельный разбор темы. Содержание конкретной обучающей программы здесь не представлено.