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