В прошлой статье мы выяснили, что переменные в 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.

Почему же список изменяется “на месте”, а число - нет?

Именно этот вопрос мы разберём в следующей статье.