В приведённом фрагменте на Python: "def foo(a=[]): a.append(1); return a" — объясните, почему последовательные вызовы foo() возвращают растущие списки, какие концепции языка это демонстрирует и как корректно изменить функцию для ожидаемого поведения
Поведение объясняется тем, что значение параметра по умолчанию вычисляется один раз при определении функции и сохраняется как один и тот же объект между вызовами. В вашем примере этот объект — изменяемый список, поэтому каждый вызов делает `append` в тот же список. Пример поведения: def foo(a=[]): a.append(1) return a Вызовы: foo() # -> [1] foo() # -> [1, 1] foo() # -> [1, 1, 1] Концепции языка, которые здесь демонстрируются: - значения параметров по умолчанию вычисляются при определении функции (а не при каждом вызове); - различие между изменяемыми (mutable) и неизменяемыми объектами; - побочные эффекты и совместное использование одного и того же объекта по ссылке. Ожидаемое (часто желаемое) поведение — создавать новый список при каждом вызове. Корректный и стандартный паттерн — использовать `None` как «стража» и создавать новый список внутри функции: def foo(a=None): if a is None: a = [] a.append(1) return a Теперь: foo() # -> [1] foo() # -> [1] Альтернативы: - принимать и возвращать новый список без изменения аргумента: `def foo(a=None): return (a or []) + [1]`; - явно копировать перед модификацией, если получили внешний список: `b = list(a)` или `a.copy()`. Используйте первый вариант (`None`-страж), если хотите, чтобы по умолчанию каждый вызов работал с новым списком.
Пример поведения:
def foo(a=[]):
a.append(1)
return a
Вызовы:
foo() # -> [1]
foo() # -> [1, 1]
foo() # -> [1, 1, 1]
Концепции языка, которые здесь демонстрируются:
- значения параметров по умолчанию вычисляются при определении функции (а не при каждом вызове);
- различие между изменяемыми (mutable) и неизменяемыми объектами;
- побочные эффекты и совместное использование одного и того же объекта по ссылке.
Ожидаемое (часто желаемое) поведение — создавать новый список при каждом вызове. Корректный и стандартный паттерн — использовать `None` как «стража» и создавать новый список внутри функции:
def foo(a=None):
if a is None:
a = []
a.append(1)
return a
Теперь:
foo() # -> [1]
foo() # -> [1]
Альтернативы:
- принимать и возвращать новый список без изменения аргумента: `def foo(a=None): return (a or []) + [1]`;
- явно копировать перед модификацией, если получили внешний список: `b = list(a)` или `a.copy()`.
Используйте первый вариант (`None`-страж), если хотите, чтобы по умолчанию каждый вызов работал с новым списком.