Любой, владеющий Python достаточно долго, был укушен (или разорван на куски) по следующей проблеме:
def foo(a=[]):
a.append(5)
return a
Новички Python ожидают, что эта функция всегда вернет список только с одним элементом: [5]
. В результате вместо этого очень разные и очень удивительные (для новичков):
>>> foo()
[5]
>>> foo()
[5, 5]
>>> foo()
[5, 5, 5]
>>> foo()
[5, 5, 5, 5]
>>> foo()
Один мой менеджер когда-то впервые встретился с этой функцией и назвал его "драматическим недостатком дизайна" языка. Я ответил, что поведение имеет основополагающее объяснение, и оно действительно очень озадачивает и неожиданно, если вы не понимаете внутренности. Тем не менее, я не смог ответить (себе) на следующий вопрос: в чем причина привязки аргумента по умолчанию при определении функции, а не при выполнении функции? Я сомневаюсь, что опытное поведение имеет практическое применение (кто действительно использовал статические переменные в C, без размножения ошибок?)
Изменить:
Бакек сделал интересный пример. Вместе с большинством ваших комментариев и, в частности, с Utaal, я подробно остановился:
>>> def a():
... print("a executed")
... return []
...
>>>
>>> def b(x=a()):
... x.append(5)
... print(x)
...
a executed
>>> b()
[5]
>>> b()
[5, 5]
Мне кажется, что решение по дизайну было относительно того, где поставить область параметров: внутри функции или "вместе" с ней?
Выполнение привязки внутри функции будет означать, что x
эффективно привязано к указанному по умолчанию, когда функция вызывается, а не определена, что-то, что могло бы представлять глубокий недостаток: строка def
была бы "гибридной" в смысл того, что часть привязки (функционального объекта) произойдет при определении и части (назначение параметров по умолчанию) во время вызова функции.
Фактическое поведение более последовательное: все из этой строки оценивается, когда эта строка выполняется, что означает определение функции.