В прошлой статье мы выяснили, что переменные в Python не хранят данные внутри себя. Вместо этого имя просто ссылается на объект.
Для чисел это выглядит довольно естественно:
x = 10
y = x
Обе переменные ссылаются на один и тот же объект со значением 10.
Пока всё выглядит вполне логично. Но стоит перейти к спискам, как поведение Python начинает удивлять.
Рассмотрим такой пример:
a = [1, 2]
b = a
a.append(3)
print(b)
Многие ожидают увидеть:
[1, 2]
Ведь мы изменяли переменную a, а не переменную b.
Однако Python выводит совсем другое:
[1, 2, 3]
Получается, что список изменился сразу в двух переменных.
На первый взгляд это выглядит странно. Кажется, будто Python каким-то образом синхронизирует значения между переменными. Но на самом деле причина гораздо проще.
Давайте вспомним вывод из прошлой статьи.
Когда мы пишем:
a = [1, 2]
создаётся объект списка, а имя a начинает на него ссылаться.
Схематично это можно изобразить так:
a ───► [1, 2]
Затем выполняется следующая строка:
b = a
Очень важно понимать, что здесь не создаётся новый список.
Python не делает копию объекта.
Вместо этого имя b начинает ссылаться на тот же самый список.
Теперь схема выглядит так:
a ─┐
├──► [1, 2]
b ─┘
Обратите внимание: список по-прежнему один.
Изменилось лишь количество имён, через которые к нему можно обратиться.
Именно поэтому следующая строка:
a.append(3)
не создаёт новый список.
Она изменяет существующий объект.
После выполнения этой операции схема становится такой:
a ─┐
├──► [1, 2, 3]
b ─┘
Поскольку оба имени указывают на один и тот же объект, изменение становится видно через обе переменные.
По сути, здесь произошло не изменение двух списков, а изменение одного списка, у которого просто есть два имени.
Эта особенность часто становится источником ошибок у начинающих разработчиков.
Например, можно написать функцию, которая изменяет переданный список, а затем долго удивляться, почему данные изменились ещё и снаружи функции.
Но с точки зрения Python никакой магии здесь нет.
Если несколько имён ссылаются на один объект, то все они видят текущее состояние этого объекта.
Возможно, сейчас у вас появился новый вопрос.
Если список можно изменить, не создавая новый объект, то почему похожий код с числами ведёт себя иначе?
Например:
x = 10
y = x
x = 20
После этого переменная y всё ещё содержит значение 10.
Почему же список изменяется “на месте”, а число - нет?
Именно этот вопрос мы разберём в следующей статье.