Python: изменяемый аргумент по умолчанию
Аргументы по умолчанию оцениваются во время компиляции функции в объект функции. При использовании этой функцией несколько раз этой функцией они являются и остаются одним и тем же объектом.
Когда они являются изменяемыми, при мутировании (например, путем добавления к нему элемента) они остаются мутированными при последовательных вызовах.
Они остаются мутированными, потому что каждый раз они являются одним и тем же объектом.
Эквивалентный код:
Поскольку список привязан к функции, когда объект функции компилируется и создается, это:
def foo(mutable_default_argument=[]): # make a list the default argument
"""function that uses a list"""
почти в точности соответствует этому:
_a_list = [] # create a list in the globals
def foo(mutable_default_argument=_a_list): # make it the default argument
"""function that uses a list"""
del _a_list # remove globals name binding
Демонстрация
Вот демонстрация - вы можете проверять, что это один и тот же объект каждый раз, когда на них ссылается
- видя, что список создается до завершения компиляции функции в объект функции,
- наблюдая, что идентификатор одинаков при каждом обращении к списку,
- заметив, что список остается измененным, когда функция, которая использует его, вызывается во второй раз,
- соблюдая порядок, в котором вывод печатается из источника (который я для вас удобно пронумеровал):
example.py
print('1. Global scope being evaluated')
def create_list():
'''noisily create a list for usage as a kwarg'''
l = []
print('3. list being created and returned, id: ' + str(id(l)))
return l
print('2. example_function about to be compiled to an object')
def example_function(default_kwarg1=create_list()):
print('appending "a" in default default_kwarg1')
default_kwarg1.append("a")
print('list with id: ' + str(id(default_kwarg1)) +
' - is now: ' + repr(default_kwarg1))
print('4. example_function compiled: ' + repr(example_function))
if __name__ == '__main__':
print('5. calling example_function twice!:')
example_function()
example_function()
и запустить его с python example.py
:
1. Global scope being evaluated
2. example_function about to be compiled to an object
3. list being created and returned, id: 140502758808032
4. example_function compiled: <function example_function at 0x7fc9590905f0>
5. calling example_function twice!:
appending "a" in default default_kwarg1
list with id: 140502758808032 - is now: ['a']
appending "a" in default default_kwarg1
list with id: 140502758808032 - is now: ['a', 'a']
Это нарушает принцип "наименьшего удивления"?
Этот порядок выполнения часто сбивает с толку новых пользователей Python. Если вы понимаете модель исполнения Python, то она становится вполне ожидаемой.
Обычная инструкция для новых пользователей Python:
Но именно поэтому обычная инструкция для новых пользователей состоит в том, чтобы вместо этого создавать их аргументы по умолчанию:
def example_function_2(default_kwarg=None):
if default_kwarg is None:
default_kwarg = []
При этом используется синглтон None в качестве сторожевого объекта, чтобы сообщить функции, получили ли мы аргумент, отличный от значения по умолчанию. Если мы не получим аргумента, то мы фактически хотим использовать новый пустой список, []
, по умолчанию.
Как указано в учебном разделе о потоке управления :
Если вы не хотите, чтобы настройки по умолчанию распределялись между последующими вызовами,
вместо этого вы можете написать такую функцию:
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L