Короткий ответ
Изменяемость — это способность объекта менять своё содержимое по тому же id() без создания нового объекта; list, dict, set изменяемы, а int, float, str, tuple, frozenset — неизменяемы, и это определяет, увидит ли вызывающий код изменения, сделанные внутри функции.
Как это работает подробнее
В Python аргументы передаются как ссылки на объекты (passing the object, не "по значению" и не классическое "по ссылке"). Если функция вызывает у переданного объекта метод, меняющий его содержимое in-place (append, pop, обновление по индексу), изменения видны снаружи, потому что это тот же объект. Если функция делает x = x + [1] или переприсваивает параметр, снаружи создаётся новый объект, и внешняя переменная не меняется.
Пример кода
example.py
def add_item(container, item):
container.append(item) # мутирует переданный list по месту
def increment(number):
number += 1 # создаёт новый int, не трогает исходный
data = [1, 2]
add_item(data, 3)
print(data) # [1, 2, 3] — изменения видны снаружи
n = 5
increment(n)
print(n) # 5 — не изменилось, int неизменяемЧто использовать на практике
- Практика. Проверить изменяемость можно через id(): если id(obj) до и после операции совпадает — объект изменили in-place, если изменился — создали новый. При проектировании функций явно документируют, мутирует ли функция входные данные, либо возвращают новый объект, чтобы не создавать скрытые побочные эффекты.
Подводные камни
- Риски. Ожидают, что
def f(n): n += 1изменит переданное число — не изменит, так как int неизменяем и+=для него создаёт новый объект. И наоборот, не ожидают, чтоdef f(lst): lst.append(1)изменит список вызывающего кода — а он изменится, так как list изменяем.