Разберите код на Python с использованием аргумента по умолчанию def f(a, lst=[]): lst.append(a); return lst — объясните распространённую ошибку, почему список накапливается между вызовами, и предложите варианты исправления
Проблема. В Python значение параметра по умолчанию вычисляется один раз — при определении функции. Если это изменяемый объект (например, список), то все вызовы функции без явного аргумента будут использовать один и тот же объект, поэтому он накапливает данные. Пример исходного кода: def f(a, lst=[]): lst.append(a) return lst Поведение при вызовах: f(1) -> [1] [1] [1] f(2) -> [1,2] [1, 2] [1,2] (ожидаем [2] [2] [2], но 리스트 накопился) Исправления (варианты). 1) Использовать sentinel None (рекомендуемый способ): def f(a, lst=None): if lst is None: lst = [] lst.append(a) return lst Теперь: f(1) -> [1] [1] [1] f(2) -> [2] [2] [2] 2) Возвращать новый список (не менять переданный): def f(a, lst=()): return list(lst) + [a] (здесь по умолчанию кортеж — неизменяемый — и каждый вызов возвращает новый список) 3) Копировать входной список внутри функции (если нужно сохранить исходный аргумент): def f(a, lst=None): if lst is None: lst = [] else: lst = list(lst) # или lst.copy() lst.append(a) return lst 4) Явно использовать фабрику (реже нужно): _sentinel = object() def f(a, lst=_sentinel): if lst is _sentinel: lst = [] lst.append(a) return lst Замечание: исходное поведение не баг интерпретатора — полезно, если вы намеренно хотите разделяемое состояние между вызовами. Если этого не нужно, применяйте один из указанных паттернов (обычно вариант с None).
Пример исходного кода:
def f(a, lst=[]):
lst.append(a)
return lst
Поведение при вызовах:
f(1) -> [1] [1] [1]
f(2) -> [1,2] [1, 2] [1,2] (ожидаем [2] [2] [2], но 리스트 накопился)
Исправления (варианты).
1) Использовать sentinel None (рекомендуемый способ):
def f(a, lst=None):
if lst is None:
lst = []
lst.append(a)
return lst
Теперь:
f(1) -> [1] [1] [1]
f(2) -> [2] [2] [2]
2) Возвращать новый список (не менять переданный):
def f(a, lst=()):
return list(lst) + [a]
(здесь по умолчанию кортеж — неизменяемый — и каждый вызов возвращает новый список)
3) Копировать входной список внутри функции (если нужно сохранить исходный аргумент):
def f(a, lst=None):
if lst is None:
lst = []
else:
lst = list(lst) # или lst.copy()
lst.append(a)
return lst
4) Явно использовать фабрику (реже нужно):
_sentinel = object()
def f(a, lst=_sentinel):
if lst is _sentinel:
lst = []
lst.append(a)
return lst
Замечание: исходное поведение не баг интерпретатора — полезно, если вы намеренно хотите разделяемое состояние между вызовами. Если этого не нужно, применяйте один из указанных паттернов (обычно вариант с None).