В приведённом Python-фрагменте def append_item(lst=[]): lst.append(1); return lst — объясните поведение при многократных вызовах, почему возникает неожиданный эффект и предложите корректные варианты реализации
Коротко — при многократных вызовах функция использует один и тот же список, созданный при определении функции, поэтому новые элементы накапливаются в нём. Почему так происходит - Значение дефолтного аргумента вычисляется один раз — в момент определения функции, а не при каждом вызове. В вашем фрагменте дефолтным значением является один и тот же объект-список, поэтому последовательные вызовы мутируют один и тот же объект. Пример поведения - При определении def append_item(lst=[]): lst.append(1) return lst вызовы вернут последовательность: - первый вызов: [1] [1] [1]
- второй вызов: [1,1] [1, 1] [1,1]
- третий вызов: [1,1,1] [1, 1, 1] [1,1,1] Корректные варианты реализации 1) Использовать `None` и создавать новый список при каждом вызове: def append_item(lst=None): if lst is None: lst = [] lst.append(1) return lst 2) Если нужно не мутировать входной список, а возвращать новый: def append_item(lst=None): if lst is None: return [1] return lst + [1] # возвращает новый список 3) Использовать кортеж (неизменяемый) как дефолт и конвертировать: def append_item(lst=()): lst = list(lst) lst.append(1) return lst Дополнение - Принцип применим ко всем изменяемым дефолтным объектам (списки, словари, множества и т.д.). Если требуется сохранять состояние между вызовами — можно намеренно использовать такое поведение, но обычно это источник ошибок.
Почему так происходит
- Значение дефолтного аргумента вычисляется один раз — в момент определения функции, а не при каждом вызове. В вашем фрагменте дефолтным значением является один и тот же объект-список, поэтому последовательные вызовы мутируют один и тот же объект.
Пример поведения
- При определении
def append_item(lst=[]):
lst.append(1)
return lst
вызовы вернут последовательность:
- первый вызов: [1] [1] [1] - второй вызов: [1,1] [1, 1] [1,1] - третий вызов: [1,1,1] [1, 1, 1] [1,1,1]
Корректные варианты реализации
1) Использовать `None` и создавать новый список при каждом вызове:
def append_item(lst=None):
if lst is None:
lst = []
lst.append(1)
return lst
2) Если нужно не мутировать входной список, а возвращать новый:
def append_item(lst=None):
if lst is None:
return [1]
return lst + [1] # возвращает новый список
3) Использовать кортеж (неизменяемый) как дефолт и конвертировать:
def append_item(lst=()):
lst = list(lst)
lst.append(1)
return lst
Дополнение
- Принцип применим ко всем изменяемым дефолтным объектам (списки, словари, множества и т.д.). Если требуется сохранять состояние между вызовами — можно намеренно использовать такое поведение, но обычно это источник ошибок.