В приведённом фрагменте Python: def f(nums=[]): nums.append(1); return nums; Объясните поведение при многократных вызовах, почему это может быть ошибкой и предложите корректные варианты реализации
Поведение: - В Python значение аргумента по умолчанию вычисляется один раз — при определении функции. Для `def f(nums=[])` одна и та же список-объект будет использоваться при всех вызовах, где аргумент не передан. - Пример: первый вызов `f()` вернёт [1] [1] [1], второй `f()` — [1,1] [1,1] [1,1], третий — [1,1,1] [1,1,1] [1,1,1] и т.д. Если передать свой список, он будет изменён: `f([0])` даст [0,1] [0,1] [0,1]. Почему это может быть ошибкой: - Неочевидное сохранение состояния между вызовами; баги трудно отлавливать. - Побочные эффекты при повторном использовании функции и в многопоточном коде. - Нарушает ожидание, что аргумент по умолчанию — «свежий» объект при каждом вызове. Правильные варианты реализации: 1) Стандартный шаблон с None (рекомендуется): def f(nums=None): if nums is None: nums = [] nums.append(1) return nums 2) Невозврат мутации — каждый вызов создаёт новый список: def f(nums=None): if nums is None: return [1] return nums + [1] 3) Явное копирование входного списка (чтобы не менять переданный объект): def f(nums=None): nums = list(nums) if nums is not None else [] nums.append(1) return nums Любой из этих вариантов устраняет нежелательное хранение состояния в аргументе по умолчанию.
- В Python значение аргумента по умолчанию вычисляется один раз — при определении функции. Для `def f(nums=[])` одна и та же список-объект будет использоваться при всех вызовах, где аргумент не передан.
- Пример: первый вызов `f()` вернёт [1] [1] [1], второй `f()` — [1,1] [1,1] [1,1], третий — [1,1,1] [1,1,1] [1,1,1] и т.д. Если передать свой список, он будет изменён: `f([0])` даст [0,1] [0,1] [0,1].
Почему это может быть ошибкой:
- Неочевидное сохранение состояния между вызовами; баги трудно отлавливать.
- Побочные эффекты при повторном использовании функции и в многопоточном коде.
- Нарушает ожидание, что аргумент по умолчанию — «свежий» объект при каждом вызове.
Правильные варианты реализации:
1) Стандартный шаблон с None (рекомендуется):
def f(nums=None):
if nums is None:
nums = []
nums.append(1)
return nums
2) Невозврат мутации — каждый вызов создаёт новый список:
def f(nums=None):
if nums is None:
return [1]
return nums + [1]
3) Явное копирование входного списка (чтобы не менять переданный объект):
def f(nums=None):
nums = list(nums) if nums is not None else []
nums.append(1)
return nums
Любой из этих вариантов устраняет нежелательное хранение состояния в аргументе по умолчанию.