Рассмотрите этот фрагмент на Python: def func(a=[]): a.append(1); return a; Объясните, почему вызовы func() ведут себя неочевидно, какие механизмы языка за этим стоят и как правильно исправить код
Коротко — потому что default-параметр `a=[]` создаётся один раз при определении функции и затем повторно используется при всех вызовах. Пояснение и исправления: Почему так происходит - В Python значения аргументов по умолчанию вычисляются один раз при создании функции (не при каждом вызове). - Если это изменяемый объект (список, словарь и т.п.), то изменения (например, `append`) сохраняются в этом же объекте и видны в последующих вызовах. - По сути все вызовы без явного аргумента получают ссылку на один и тот же список. Демонстрация (поведение) ```python def func(a=[]): a.append(1) return a print(func()) # [1] print(func()) # [1, 1] print(func()) # [1, 1, 1] ``` Как это исправить (рекомендованный вариант) - Использовать sentinel `None` и при необходимости создавать новый список внутри функции: ```python def func(a=None): if a is None: a = [] a.append(1) return a ``` Теперь каждый вызов без аргумента создаёт новый список: ```python print(func()) # [1] print(func()) # [1] ``` Альтернативы и примечания - Если хотите не мутировать переданный список, сделайте копию: `a = list(a)` или `a = a.copy()`. - Если вам нужно именно одноразовое кэширование/накопление состояния между вызовами, то использование изменяемого default может быть намеренным (но это редкий паттерн и его стоит явно документировать). - Можно также возвращать новый список без мутаций: `return (a or []) + [1]`. Дополнительно: можно посмотреть текущее значение default через `func.__defaults__`, там хранится объект по умолчанию.
Почему так происходит
- В Python значения аргументов по умолчанию вычисляются один раз при создании функции (не при каждом вызове).
- Если это изменяемый объект (список, словарь и т.п.), то изменения (например, `append`) сохраняются в этом же объекте и видны в последующих вызовах.
- По сути все вызовы без явного аргумента получают ссылку на один и тот же список.
Демонстрация (поведение)
```python
def func(a=[]):
a.append(1)
return a
print(func()) # [1]
print(func()) # [1, 1]
print(func()) # [1, 1, 1]
```
Как это исправить (рекомендованный вариант)
- Использовать sentinel `None` и при необходимости создавать новый список внутри функции:
```python
def func(a=None):
if a is None:
a = []
a.append(1)
return a
```
Теперь каждый вызов без аргумента создаёт новый список:
```python
print(func()) # [1]
print(func()) # [1]
```
Альтернативы и примечания
- Если хотите не мутировать переданный список, сделайте копию: `a = list(a)` или `a = a.copy()`.
- Если вам нужно именно одноразовое кэширование/накопление состояния между вызовами, то использование изменяемого default может быть намеренным (но это редкий паттерн и его стоит явно документировать).
- Можно также возвращать новый список без мутаций: `return (a or []) + [1]`.
Дополнительно: можно посмотреть текущее значение default через `func.__defaults__`, там хранится объект по умолчанию.