Рассмотрите следующий Python-код: "def f(x=[]): x.append(1); return x" — почему поведение функции может быть неожиданным для новичков и какие практики языка объясняют это
Коротко: неожиданность возникает потому, что значение аргумента по умолчанию вычисляется один раз при определении функции и сохраняется как тот же самый объект; список — изменяемый объект, поэтому каждое вызов функции меняет один и тот же список. В результате последовательные вызовы дают накопительный эффект. Объяснение по пунктам: - При выполнении `def f(x=[]): ...` объект `[]` создаётся один раз (при загрузке/компиляции функции), а не при каждом вызове. - Параметр по умолчанию связывается с этим одним объектом; при мутирующих операциях (например, `append`) изменения сохраняются между вызовами. - Это поведение вытекает из семантики Python: параметры по умолчанию — это ссылки на объекты, созданные один раз. Мутируемость (list) vs. иммутабельность (tuple, int, str) играет роль: с иммутабельными типами подобных сюрпризов обычно не бывает. Примеры поведения: - первый вызов `f()` вернёт [1][1][1]; - второй вызов `f()` вернёт [1,1][1,1][1,1]; - вызов `f([2])` вернёт [2,1][2,1][2,1] (если передан свой список, используется он). Рекомендация (шаблон, избегающий проблемы): def f(x=None): if x is None: x = [] x.append(1) return x Здесь используется «sentinel» `None`, чтобы при каждом вызове при отсутствии аргумента создавать новый список.
Объяснение по пунктам:
- При выполнении `def f(x=[]): ...` объект `[]` создаётся один раз (при загрузке/компиляции функции), а не при каждом вызове.
- Параметр по умолчанию связывается с этим одним объектом; при мутирующих операциях (например, `append`) изменения сохраняются между вызовами.
- Это поведение вытекает из семантики Python: параметры по умолчанию — это ссылки на объекты, созданные один раз. Мутируемость (list) vs. иммутабельность (tuple, int, str) играет роль: с иммутабельными типами подобных сюрпризов обычно не бывает.
Примеры поведения:
- первый вызов `f()` вернёт [1][1][1];
- второй вызов `f()` вернёт [1,1][1,1][1,1];
- вызов `f([2])` вернёт [2,1][2,1][2,1] (если передан свой список, используется он).
Рекомендация (шаблон, избегающий проблемы):
def f(x=None):
if x is None:
x = []
x.append(1)
return x
Здесь используется «sentinel» `None`, чтобы при каждом вызове при отсутствии аргумента создавать новый список.