При выполнении кода на Python: def add_item(item, lst=[]): lst.append(item); return lst; — объясните, почему поведение отличается от ожиданий, покажите исправления и обсудите сценарии, где поведение с mutable default полезно
Коротко — причина: значение параметра по умолчанию вычисляется один раз при определении функции и переиспользуется при последующих вызовах. Для изменяемого объекта (например, списка) это значит, что все вызовы без явного аргумента будут работать с одним и тем же списком. Пример (неожиданное поведение): ```python def add_item(item, lst=[]): lst.append(item) return lst print(add_item('a')) # -> ['a'] print(add_item('b')) # -> ['a', 'b'] (ожидают ['b']) ``` Здесь список по умолчанию создан один раз при определении функции (т.е. 111 раз) и накапливает элементы при каждом новом вызове. Как исправить (рекомендуемый способ): ```python def add_item(item, lst=None): if lst is None: lst = [] lst.append(item) return lst print(add_item('a')) # -> ['a'] print(add_item('b')) # -> ['b'] ``` Альтернативы: - Передавать явно новый список при каждом вызове: `add_item('x', [])`. - Использовать фабрику при создании объектов/классов (например, `field(default_factory=list)` в dataclasses). Когда поведение с изменяемым default полезно: - Если вы намеренно хотите хранить состояние между вызовами (аккумулятор/журнал): например, простая реализация recorder, который сохраняет все переданные значения. - Для простого кеширования/мемоизации, когда функция должна запоминать результаты (но лучше явно документировать это или использовать структуры/декораторы для кеша). - В редких случаях — повторное использование заранее созданного списка для экономии аллокаций, если это намеренно и контролируемо. Совет: если функция должна вести себя «чисто» (без сохранения состояния) — используйте шаблон с `None`. Если же хранение состояния нужно — документируйте это явно, чтобы поведение не вызывало сюрпризов.
Пример (неожиданное поведение):
```python
def add_item(item, lst=[]):
lst.append(item)
return lst
print(add_item('a')) # -> ['a']
print(add_item('b')) # -> ['a', 'b'] (ожидают ['b'])
```
Здесь список по умолчанию создан один раз при определении функции (т.е. 111 раз) и накапливает элементы при каждом новом вызове.
Как исправить (рекомендуемый способ):
```python
def add_item(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
print(add_item('a')) # -> ['a']
print(add_item('b')) # -> ['b']
```
Альтернативы:
- Передавать явно новый список при каждом вызове: `add_item('x', [])`.
- Использовать фабрику при создании объектов/классов (например, `field(default_factory=list)` в dataclasses).
Когда поведение с изменяемым default полезно:
- Если вы намеренно хотите хранить состояние между вызовами (аккумулятор/журнал): например, простая реализация recorder, который сохраняет все переданные значения.
- Для простого кеширования/мемоизации, когда функция должна запоминать результаты (но лучше явно документировать это или использовать структуры/декораторы для кеша).
- В редких случаях — повторное использование заранее созданного списка для экономии аллокаций, если это намеренно и контролируемо.
Совет: если функция должна вести себя «чисто» (без сохранения состояния) — используйте шаблон с `None`. Если же хранение состояния нужно — документируйте это явно, чтобы поведение не вызывало сюрпризов.