Рассмотрите следующий фрагмент Python: def foo(x=[]): x.append(1); return x; Объясните, почему поведение функции может быть неожиданным при повторных вызовах, как это связано с моделями памяти и как исправить функцию
Проблема в том, что значение параметра по умолчанию вычисляется один раз — при определении функции — и если это изменяемый объект (например список), то все последующие вызовы будут использовать один и тот же объект. Пример (поведение может быть неожиданным): def foo(x=[]): x.append(1) return x Вызовы: - первый: foo() вернёт [1][1][1]
- второй: foo() вернёт [1,1][1, 1][1,1]
(потому что вторая функция модифицирует тот же список) Почему так происходит (модель памяти): - при определении функции создаётся объект по умолчанию (список) и хранится в объекте функции; - при каждом вызове имя xxx привязывается к этому одному и тому же объекту (ссылке); - список — изменяемый объект, поэтому операции вроде append меняют сам объект, и изменения видны при последующих вызовах. Как исправить (стандартный приём): def foo(x=None): if x is None: x = [] x.append(1) return x Альтернативы: - создавать новый список при необходимости: return (x or []) + [1] (если нежелательно менять переданный список); - использовать неизменяемые значения по умолчанию (например NoneNoneNone или кортежи), либо фабрики (например в dataclasses — field(default_factory=...)).
Пример (поведение может быть неожиданным):
def foo(x=[]):
x.append(1)
return x
Вызовы:
- первый: foo() вернёт [1][1][1] - второй: foo() вернёт [1,1][1, 1][1,1] (потому что вторая функция модифицирует тот же список)
Почему так происходит (модель памяти):
- при определении функции создаётся объект по умолчанию (список) и хранится в объекте функции;
- при каждом вызове имя xxx привязывается к этому одному и тому же объекту (ссылке);
- список — изменяемый объект, поэтому операции вроде append меняют сам объект, и изменения видны при последующих вызовах.
Как исправить (стандартный приём):
def foo(x=None):
if x is None:
x = []
x.append(1)
return x
Альтернативы:
- создавать новый список при необходимости: return (x or []) + [1] (если нежелательно менять переданный список);
- использовать неизменяемые значения по умолчанию (например NoneNoneNone или кортежи), либо фабрики (например в dataclasses — field(default_factory=...)).