В коде на Python: def append_to_list(x, lst=[]): lst.append(x); return lst. Объясните проблему с использованием изменяемых аргументов по умолчанию и предложите несколько безопасных альтернатив в разных контекстах
Проблема: значение аргумента по умолчанию вычисляется один раз при определении функции, поэтому если это изменяемый объект (например список), все вызовы без явно переданного аргумента будут использовать и мутировать один и тот же объект. Пример (небезопасно): ``` def append_to_list(x, lst=[]): lst.append(x) return lst ``` Поведение: - первый вызов append_to_list(1) вернёт [1][1][1], - второй вызов append_to_list(2) вернёт [1,2][1, 2][1,2], - третий вызов append_to_list(3) вернёт [1,2,3][1, 2, 3][1,2,3]. Безопасные альтернативы 1) Стандартный паттерн с None (рекомендуется): ``` def append_to_list(x, lst=None): if lst is None: lst = [] lst.append(x) return lst ``` Используйте, когда нужно новый список по умолчанию при каждом вызове. 2) Возврат нового списка (функция не мутирует вход): ``` def append_to_list(x, lst=None): if lst is None: return [x] return lst + [x] # создает новый список ``` Полезно в функциональном стиле или когда нельзя изменять переданный объект. 3) Копирование входного списка (если нужно сохранить переданный объект неизменным): ``` def append_to_list(x, lst=None): lst_copy = list(lst) if lst is not None else [] lst_copy.append(x) return lst_copy ``` 4) Если нужен именно накопитель между вызовами (явное сохранение состояния): - явный глобальный/замыкание/атрибут функции: ``` def make_appender(): lst = [] def append(x): lst.append(x) return lst return append app = make_appender() ``` или ``` def append_to_list(x, _state=[]): # сознательно использовать один список, но документировать это _state.append(x) return _state ``` (использовать только если такое поведение желаемо и понятно). 5) Для классов и dataclass: - В __init__ присваивайте новый список: `self.lst = []`, не используйте `def __init__(..., lst=[])`. - В dataclass: ``` from dataclasses import dataclass, field @dataclass class C: lst: list = field(default_factory=list) ``` Краткое правило: не используйте изменяемые объекты как значения по умолчанию, если вы не хотите, чтобы состояние сохранялось между вызовами; вместо этого используйте None + создание нового объекта или default_factory (dataclass).
Пример (небезопасно):
```
def append_to_list(x, lst=[]):
lst.append(x)
return lst
```
Поведение:
- первый вызов append_to_list(1) вернёт [1][1][1],
- второй вызов append_to_list(2) вернёт [1,2][1, 2][1,2],
- третий вызов append_to_list(3) вернёт [1,2,3][1, 2, 3][1,2,3].
Безопасные альтернативы
1) Стандартный паттерн с None (рекомендуется):
```
def append_to_list(x, lst=None):
if lst is None:
lst = []
lst.append(x)
return lst
```
Используйте, когда нужно новый список по умолчанию при каждом вызове.
2) Возврат нового списка (функция не мутирует вход):
```
def append_to_list(x, lst=None):
if lst is None:
return [x]
return lst + [x] # создает новый список
```
Полезно в функциональном стиле или когда нельзя изменять переданный объект.
3) Копирование входного списка (если нужно сохранить переданный объект неизменным):
```
def append_to_list(x, lst=None):
lst_copy = list(lst) if lst is not None else []
lst_copy.append(x)
return lst_copy
```
4) Если нужен именно накопитель между вызовами (явное сохранение состояния):
- явный глобальный/замыкание/атрибут функции:
```
def make_appender():
lst = []
def append(x):
lst.append(x)
return lst
return append
app = make_appender()
```
или
```
def append_to_list(x, _state=[]): # сознательно использовать один список, но документировать это
_state.append(x)
return _state
```
(использовать только если такое поведение желаемо и понятно).
5) Для классов и dataclass:
- В __init__ присваивайте новый список: `self.lst = []`, не используйте `def __init__(..., lst=[])`.
- В dataclass:
```
from dataclasses import dataclass, field
@dataclass
class C:
lst: list = field(default_factory=list)
```
Краткое правило: не используйте изменяемые объекты как значения по умолчанию, если вы не хотите, чтобы состояние сохранялось между вызовами; вместо этого используйте None + создание нового объекта или default_factory (dataclass).