Рассмотрите фрагмент на Python: def add_item(item, lst=[]): lst.append(item); return lst; — объясните поведение этого кода при последовательных вызовах, укажите потенциальные ошибки и предложите корректные альтернативы на нескольких уровнях (быстрое исправление, реструктуризация API)

12 Окт в 14:18
2 +1
0
Ответы
1

Коротко — что происходит

В Python значение аргумента по умолчанию вычисляется один раз при определении функции. В вашем примере дефолтный список создаётся один раз и используется повторно при каждом вызове, когда параметр lst не передан.Следствие: последовательные вызовы накапливают элементы в одном и том же списке.

Пример поведения

def add_itemitem,lst=[]item, lst=[]item,lst=[]:
lst.appenditemitemitem return lst
printadditem(1)add_item(1)addi tem(1) # 111 printadditem(2)add_item(2)addi tem(2) # 1,21, 21,2 <- неожиданный для многих результат
printadditem(3)add_item(3)addi tem(3) # 1,2,31, 2, 31,2,3

Почему это плохо / потенциальные ошибки

Неочевидное накопление состояния — баги трудно отследить.Такое поведение распространяется на любые изменяемые дефолтные значения dict,set,объектыdict, set, объектыdict,set,объекты.Проблемы с многопоточностью: список общий между вызовами из разных потоков.Нарушение принципа минимальной неожиданности API — функция кажется чистой, но имеет сайд-эффекты.

Быстрое исправление самыйраспространённыйпаттернсамый распространённый паттернсамыйраспространённыйпаттерн

Использовать None как дефолт и создавать новый список внутри:
def add_itemitem,lst=Noneitem, lst=Noneitem,lst=None:
if lst is None:
lst = lst.appenditemitemitem return lst

Теперь:
printadditem(1)add_item(1)addi tem(1) -> 111 printadditem(2)add_item(2)addi tem(2) -> 222 # независимые вызовы

Альтернативы / реструктуризация API несколькоуровнейнесколько уровнейнесколькоуровней

1) Неп изменять входной список — чистая функция немутируетаргументыне мутирует аргументынемутируетаргументы

def add_itemitem,lst=Noneitem, lst=Noneitem,lst=None:
if lst is None:
return itemitemitem return lst + itemitemitem # возвращает новый список

Плюсы: безопасно в многопоточных средах, легче тестировать.

2) Явно требовать список чёткопоказать,чтопроисходитмутациячётко показать, что происходит мутациячёткопоказать,чтопроисходитмутация

def append_toitem,lstitem, lstitem,lst:
lst.appenditemitemitem return lst

Плюс: API явно мутабельный, нельзя по ошибке забыть передать.

3) Если нужно сохранять состояние между вызовами — сделать это явно класс/замыканиекласс/замыканиекласс/замыкание

С классом:
class Collector:
def __init__selfselfself:
self.items = def addself,itemself, itemself,item:
self.items.appenditemitemitem return self.itemsС замыканием:
def make_collector:
items = def additemitemitem:
items.appenditemitemitem return items
return add

Плюс: состояние стало явным, можно создавать несколько независимых коллекций.

4) Если нужен дефолт‑фабричный паттерн каквdataclassesкак в dataclassesкаквdataclasses:

Для параметров функции используют None-сентинел или фабрику, но в сигнатуре фабрика в явном виде:
def add_itemitem,listfactory=list,lst=Noneitem, list_factory=list, lst=Noneitem,listf actory=list,lst=None:
if lst is None:
lst = list_factory lst.appenditemitemitem return lst

Это полезно, если нужен специфичный тип контейнера.

5) Если нужно защищать от непреднамеренной мутации переданного списка — работать с копией

def add_item_safeitem,lst=Noneitem, lst=Noneitem,lst=None:
if lst is None:
lst = else:
lst = listlstlstlst # копируем
lst.appenditemitemitem return lst

Рекомендация

Если вы не хотите сохранять состояние между вызовами — используйте паттерн с lst=None быстроеисправлениебыстрое исправлениебыстроеисправление или полностью неперезаписывающую возвращатьновыйсписоквозвращать новый списоквозвращатьновыйсписок.Если вам нужно именно разделяемое состояние — сделайте его явным классилизамыканиекласс или замыканиеклассилизамыкание и задокументируйте.

Дополнительные замечания

Этот трюк касается любых изменяемых дефолтных значений.Документируйте поведение функции, чтобы не вводить в заблуждение пользователей API.
12 Окт в 15:30
Не можешь разобраться в этой теме?
Обратись за помощью к экспертам
Гарантированные бесплатные доработки в течение 1 года
Быстрое выполнение от 2 часов
Проверка работы на плагиат
Поможем написать учебную работу
Прямой эфир