В питоне всё, что можно положить в переменную, является объектом. А сама переменная является ссылкой на этот объект.
a = 1
a = 2
b = a
Объекты «живут» в своём мире независимо от переменных, а переменные — один из способов обратиться извне к миру объектов.
Так в примере выше объект Целое-число-1 продолжает жить даже после того, как мы поменяли переменную a
.
Объекты в питоне бывают двух значительно отличающихся сортов: изменяемые (mutable) и неизменяемые (immutable).
Неизменяемыми являются целые и действительные числа (int
, float
), строки (str
), последовательности байтов (бинарные данные, bytes
), а также кортежи, все элементы которых неизменяемы (tuple
).
Напротив, списки (list
), словари (dict
) и множества (set
) являются изменяемыми.
Что всё это значит?
Неизменяемые объекты обладают замечательным свойством: они не могут измениться в результате работы программы.
Если некоторая переменная «смотрит» на неизменяемый объект, то можно быть уверенным, что её значение не поменяется, если только ей не присвоить ссылку на другой объект.
Например, в результате выполнения кода a = 1
в переменной a
хранится ссылка на объект Целое-число-1, и что бы не происходило с другими переменными, a
будет всегда равно 1.
Изменяемые объекты не обладают таким постоянством. Они скорее напоминают контейнер для хранения: контейнер остаётся на месте, а вот содержимое может сильно измениться. Что всё это для нас значит? Ну, например, если передать список в «чужую» функцию, то его могут испортить до неузнаваемости:
def list_destroyer(l): l[0] = 'ha' l[1] = 'hi' l[2] = 'ho' a = [1, 2, 3] print(a) # -> [1, 2, 3] list_destroyer(a) print(a) # -> ['ha', 'hi', 'ho']Здесь можно поиграться с этим примером и посмотреть на то, что происходит с ссылками:
Обратите внимание, при передаче списка в функцию можно модифицировать элементы списка при помощи присваиваний A[i] = ...
, методов pop
, append
, insert
и т.д.
Это не является модификацией списка, как целого, т.е. имя A
по-прежнему указывает на тот же список, при этом
содержимое списка меняется.
Не всегда такое поведение является ожидаемым:
a = [1, 2, 3] b = a b.insert(0, 'ops') b.pop() print(b) # -> ['ops', 1, 2] print(a) # -> ['ops', 1, 2]
Присваивание b = a
не создает новый список,
а всего лишь делает b
ссылкой на уже
существующий список.
Для того, чтобы «сохранить» старый список, нужно создавать его копию:
a = [1, 2, 3] b = a.copy() b.insert(0, 'ops') b.pop() print(b) # -> ['ops', 1, 2] print(a) # -> [1, 2, 3]