В представленном Python-фрагменте: def append_number(x, lst=[]): lst.append(x); return lst; объясните, почему этот код ведёт себя неожиданно при повторных вызовах, как это связано с семантикой объектов и областей видимости, и предложите корректные альтернативы

24 Дек 2025 в 13:07
44 +1
0
Ответы
1
Проблема и причина (кратко)
- При выполнении определения функции значение аргумента по умолчанию вычисляется один раз и привязывается к имени параметра. В вашем примере при определении создаётся один объект списка и затем переиспользуется при каждом вызове функции. Поскольку списки изменяемы, мутации в одном вызове сохраняются для последующих.
- Следствие: первый вызов `append_number(1)` вернёт [1][1][1], второй `append_number(2)` вернёт [1,2][1, 2][1,2] — не новый список, а тот же объект с накопленными значениями.
Как это связано с семантикой объектов и областей видимости
- Значение default-аргумента вычисляется в момент определения функции (не при вызове). Параметр ссылается на один и тот же объект (идентичность), а не на «новое значение» при каждом вызове.
- Это поведение связано с семантикой ссылочных типов в Python: переменные хранят ссылки на объекты; если объект изменяем — изменения видны всем ссылкам на него.
Корректные альтернативы
1) Простой идиоматический вариант — использовать `None` как sentinel и создавать новый список при вызове:
def append_number(x, lst=None):
if lst is None:
lst = []
lst.append(x)
return lst
Пример: append_number(1) → [1][1][1], следующий append_number(2) → [2][2][2].
2) Не мутировать переданный список, а возвращать новый:
def append_number(x, lst=None):
if lst is None:
return [x]
return lst + [x]
Это безопасно, т.к. создаётся новый список при каждом вызове.
3) Если функция должна принимать существующий список, но не менять его — сделать копию перед изменением:
def append_number(x, lst=None):
if lst is None:
lst = []
else:
lst = list(lst) # копия
lst.append(x)
return lst
4) Если вам нужен разделяемый по умолчанию единый контейнер — делайте это явно и задокументируйте (но это редко желаемо):
_default = []
def append_number(x, lst=_default):
lst.append(x)
return lst
Замечание: будьте осторожны с побочными эффектами и многопоточностью; предпочтительны варианты (1) или (2).
Короткая суть: не используйте изменяемый объект как значение аргумента по умолчанию, если вы ожидаете «пустой контейнер» при каждом вызове — используйте `None` + создание нового контейнера.
24 Дек 2025 в 13:15
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир