В языке Python показан фрагмент: def add_to_list(x, l=[]): l.append(x); return l — объясните неожиданное поведение и способы его устранения, обсудите различия в семантике между изменяемыми и неизменяемыми объектами
Коротко — причина в том, что значение аргумента по умолчанию вычисляется один раз при определении функции и если это изменяемый объект (например список), то все вызовы будут использовать один и тот же объект. Пример неожиданного поведения: - функция: def add_to_list(x, l=[]): l.append(x) return l - вызовы: add_to_list(1) → [1][1][1] add_to_list(2) → [1,2][1, 2][1,2] add_to_list(3, []) → [3][3][3] (здесь передан новый список) Почему так: при определении функции создаётся один объект списка (по умолчанию) и каждый последующий вызов без явного аргумента использует тот же самый объект, поэтому изменения (append) аккумулируются. Способы устранения: 1. Использовать sentinel `None` и создавать новый список внутри: def add_to_list(x, l=None): if l is None: l = [] l.append(x) return l 2. Создавать новый объект прямо в сигнатуре (если подходит), например использовать кортежы как неизменяемый контейнер, или явно копировать: def f(l=[]): l = l[:] # копия, затем менять 3. Использовать фабрики/декораторы / dataclass default_factory (в контексте классов) — подходящие средства создают новое значение при каждом вызове/инициализации. Различия семантики изменяемых и неизменяемых объектов (кратко): - Изменяемые объекты (list, dict, set и т.д.): - Операции "внутри" объекта (например, `append`, `pop`, присвоение по индексу) изменяют сам объект. Если несколько имён ссылаются на один объект, они «видят» эти изменения. - Передача в функцию передаёт ссылку на объект; функция может изменить объект, и изменения будут видны снаружи. - Неизменяемые объекты (int, float, str, tuple и т.д.): - Операции создают новый объект; исходный остаётся неизменным. - В функции любые присвоения имени (например `x += 1` для `int`) создают новый объект и не влияют на внешний объект. Короткий пример различий: - int: a = 111
def inc(x): x += 111 # создаёт новый int, внешний a не меняется - list: b = [] def add_one(lst): lst.append(111) # меняет объект b, внешний b видит изменение Итого: избегайте изменяемых объектов как значений по умолчанию; применяйте паттерн с `None` или всегда создавайте/копируйте нужный объект внутри функции.
Пример неожиданного поведения:
- функция:
def add_to_list(x, l=[]):
l.append(x)
return l
- вызовы:
add_to_list(1) → [1][1][1]
add_to_list(2) → [1,2][1, 2][1,2]
add_to_list(3, []) → [3][3][3] (здесь передан новый список)
Почему так: при определении функции создаётся один объект списка (по умолчанию) и каждый последующий вызов без явного аргумента использует тот же самый объект, поэтому изменения (append) аккумулируются.
Способы устранения:
1. Использовать sentinel `None` и создавать новый список внутри:
def add_to_list(x, l=None):
if l is None:
l = []
l.append(x)
return l
2. Создавать новый объект прямо в сигнатуре (если подходит), например использовать кортежы как неизменяемый контейнер, или явно копировать:
def f(l=[]):
l = l[:] # копия, затем менять
3. Использовать фабрики/декораторы / dataclass default_factory (в контексте классов) — подходящие средства создают новое значение при каждом вызове/инициализации.
Различия семантики изменяемых и неизменяемых объектов (кратко):
- Изменяемые объекты (list, dict, set и т.д.):
- Операции "внутри" объекта (например, `append`, `pop`, присвоение по индексу) изменяют сам объект. Если несколько имён ссылаются на один объект, они «видят» эти изменения.
- Передача в функцию передаёт ссылку на объект; функция может изменить объект, и изменения будут видны снаружи.
- Неизменяемые объекты (int, float, str, tuple и т.д.):
- Операции создают новый объект; исходный остаётся неизменным.
- В функции любые присвоения имени (например `x += 1` для `int`) создают новый объект и не влияют на внешний объект.
Короткий пример различий:
- int:
a = 111 def inc(x):
x += 111 # создаёт новый int, внешний a не меняется
- list:
b = []
def add_one(lst):
lst.append(111) # меняет объект b, внешний b видит изменение
Итого: избегайте изменяемых объектов как значений по умолчанию; применяйте паттерн с `None` или всегда создавайте/копируйте нужный объект внутри функции.