В приведённом фрагменте Python: "def f(a=[]): a.append(1); return a" — объясните, почему поведение функции на последовательных вызовах может быть неожиданным, и предложите несколько способов изменить код, чтобы добиться ожидаемой семантики
Кратко — потому что значение параметра по умолчанию вычисляется один раз при определении функции, а не при каждом вызове. В вашем примере список [][][] создаётся один раз и затем мутируется методом .append().append().append(), поэтому все последующие вызовы работают с той же самой списком. Почему так получается (коротко): - При определении `def f(a=[]): ...` объект списка создаётся единожды. - Вызов `f()` делает `a.append(1)`, то есть мутирует этот единственный список. - Поэтому последовательные вызовы дают накопительный эффект: первый вызов вернёт [1][1][1], второй — [1,1][1, 1][1,1], и т.д. Способы добиться ожидаемой семантики (каждый вызов — новый список): 1) Использовать sentinel `None` (рекомендуется): def f(a=None): if a is None: a = [] a.append(1) return a 2) Не мутировать аргумент, возвращать новую структуру: def f(a=None): if a is None: a = [] return a + [1] # создаёт новый список 3) Использовать неизменяемый дефолт (кортеж) и преобразовывать в список: def f(a=()): a = list(a) a.append(1) return a 4) Если нужен именно общий накопитель (т.е. поведение как в исходном примере), сделать это явно (чтобы код был явным): def f(a=None): if a is None: a = f._acc a.append(1) return a f._acc = [] Пример поведения исходной функции: первый вызов `f()` → [1][1][1], второй вызов `f()` → [1,1][1, 1][1,1]. Резюме: избегайте изменяемых объектов в качестве дефолтных значений; используйте `None` или возвращайте новую коллекцию.
Почему так получается (коротко):
- При определении `def f(a=[]): ...` объект списка создаётся единожды.
- Вызов `f()` делает `a.append(1)`, то есть мутирует этот единственный список.
- Поэтому последовательные вызовы дают накопительный эффект: первый вызов вернёт [1][1][1], второй — [1,1][1, 1][1,1], и т.д.
Способы добиться ожидаемой семантики (каждый вызов — новый список):
1) Использовать sentinel `None` (рекомендуется):
def f(a=None):
if a is None:
a = []
a.append(1)
return a
2) Не мутировать аргумент, возвращать новую структуру:
def f(a=None):
if a is None:
a = []
return a + [1] # создаёт новый список
3) Использовать неизменяемый дефолт (кортеж) и преобразовывать в список:
def f(a=()):
a = list(a)
a.append(1)
return a
4) Если нужен именно общий накопитель (т.е. поведение как в исходном примере), сделать это явно (чтобы код был явным):
def f(a=None):
if a is None:
a = f._acc
a.append(1)
return a
f._acc = []
Пример поведения исходной функции: первый вызов `f()` → [1][1][1], второй вызов `f()` → [1,1][1, 1][1,1].
Резюме: избегайте изменяемых объектов в качестве дефолтных значений; используйте `None` или возвращайте новую коллекцию.