Рассмотрите следующий фрагмент Python: def append_item(lst=[]): lst.append(1); return lst; — что делает этот код при многократных вызовах, в чём проблема и как её исправить, приведите альтернативные безопасные реализации
Что делает: при многократных вызовах используется один и тот же список (mutable default), поэтому он накапливает значения. Например: - первый вызов append_item() возвращает [1] [1] [1]
- второй вызов append_item() возвращает [1,1] [1,1] [1,1]
- третий — [1,1,1] [1,1,1] [1,1,1] В чём проблема: значение параметра по умолчанию вычисляется один раз при определении функции и затем разделяется всеми вызовами. Для изменяемых объектов (списки, словари и т.п.) это ведёт к неожиданному общему состоянию между вызовами. Решение (рекомендуемое): использовать sentinel (обычно None) и создавать новый список внутри функции: def append_item(lst=None): if lst is None: lst = [] lst.append(1) return lst Альтернативные безопасные реализации: 1) Возвращать новый список, не мутируя переданный: def append_item(lst=None): return (lst or []) + [1] 2) Копировать переданный аргумент, чтобы не изменять оригинал: def append_item(lst=None): if lst is None: lst = [] else: lst = list(lst) # shallow copy lst.append(1) return lst 3) Явно требовать список от вызывающего и не давать default (если нужно явно избегать скрытых состояний): def append_item(lst): lst.append(1) return lst Примечание: первая (None-sentinel) версия — наиболее распространённая и понятная.
- первый вызов append_item() возвращает [1] [1] [1] - второй вызов append_item() возвращает [1,1] [1,1] [1,1] - третий — [1,1,1] [1,1,1] [1,1,1]
В чём проблема: значение параметра по умолчанию вычисляется один раз при определении функции и затем разделяется всеми вызовами. Для изменяемых объектов (списки, словари и т.п.) это ведёт к неожиданному общему состоянию между вызовами.
Решение (рекомендуемое): использовать sentinel (обычно None) и создавать новый список внутри функции:
def append_item(lst=None):
if lst is None:
lst = []
lst.append(1)
return lst
Альтернативные безопасные реализации:
1) Возвращать новый список, не мутируя переданный:
def append_item(lst=None):
return (lst or []) + [1]
2) Копировать переданный аргумент, чтобы не изменять оригинал:
def append_item(lst=None):
if lst is None:
lst = []
else:
lst = list(lst) # shallow copy
lst.append(1)
return lst
3) Явно требовать список от вызывающего и не давать default (если нужно явно избегать скрытых состояний):
def append_item(lst):
lst.append(1)
return lst
Примечание: первая (None-sentinel) версия — наиболее распространённая и понятная.