Почему использование arg = None решает проблему изменяемого аргумента Python по умолчанию?
Я нахожусь в точке изучения Python, где я имею дело с проблемой изменяемого аргумента по умолчанию.
# BAD: if 'a_list' is not passed in, the default will wrongly retain its contents between successive function calls
def bad_append(new_item, a_list=[]):
a_list.append(new_item)
return a_list
# GOOD: if 'a_list' is not passed in, the default will always correctly be []
def good_append(new_item, a_list=None):
if a_list is None:
a_list = []
a_list.append(new_item)
return a_list
Я понимаю, что a_list
инициализируется только тогда, когда впервые встречается оператор def
, и поэтому последующие вызовы bad_append
используют один и тот же объект списка.
Я не понимаю, почему good_append
работает по-другому. Похоже, a_list
все равно будет инициализирован только один раз; следовательно, оператор if
будет истинным только при первом вызове функции, что означает, что a_list
будет сброшен только в []
при первом вызове, что означает, что он все равно будет накапливать все прошлые значения new_item
и все еще будет содержать ошибки.
Почему не так? Какую концепцию мне не хватает? Как a_list
очищается каждый раз, good_append
запускается good_append
?
Ответы
Ответ 1
Значение по умолчанию a_list
(или любое другое значение по умолчанию, если на то пошло) хранится в интерьерах функций после его инициализации и поэтому может быть любым образом изменено:
>>> def f(x=[]): return x
...
>>> f.func_defaults
([],)
>>> f.func_defaults[0] is f()
Таким образом, значение в func_defaults
является тем же самым, что является общеизвестной внутренней функцией (и возвращается в моем примере для доступа к нему извне.
IOW, то, что происходит при вызове f()
, является неявным x = f.func_defaults[0]
. Если впоследствии этот объект будет изменен, вы сохраните эту модификацию.
В отличие от присваивания внутри функция всегда получает новый []
. Любая модификация будет продолжаться до тех пор, пока последняя ссылка на []
не исчезнет; при следующем вызове функции создается новый []
.
IOW снова, это не так, что []
получает один и тот же объект при каждом выполнении, но он (в случае аргумента по умолчанию) выполняется только один раз и затем сохраняется.
Ответ 2
Похоже, что a_list все равно будет инициализирован только один раз
"инициализация" не является чем-то, что происходит с переменными в Python, потому что переменные в Python - это просто имена. "Инициализация" происходит только с объектами, и это делается с помощью метода класса __init__
.
Когда вы пишете a = 0
, это назначение. Это означает, что "a
относится к объекту, который описывается выражением 0
". Это не инициализация; a
может называть любое другое из любого типа в любое более позднее время, и это происходит в результате назначения чего-то еще a
. Назначение - это просто назначение. Первый не является особенным.
Когда вы пишете def good_append(new_item, a_list=None)
, это не "инициализация" a_list
. Он устанавливает внутреннюю ссылку на объект, результат оценки None
, так что, когда good_append
вызывается без второго параметра, этот объект автоматически присваивается a_list
.
Значение a_list получит только reset to [] при первом вызове
Нет, a_list
устанавливается в []
в любое время, когда a_list
есть None
. То есть, когда либо None
передается явно, либо аргумент опущен.
Проблема с []
возникает из-за того, что выражение []
оценивается только один раз в этом контексте. Когда функция компилируется, оценивается []
, создается объект специфический список, который пуст для запуска, и этот объект используется по умолчанию.
Как a_list
очищается каждый раз, когда выполняется good_append
?
Это не так. Это не обязательно.
Вы знаете, как проблема описывается как "изменяемые аргументы по умолчанию"?
None
не изменяет.
Проблема возникает, когда вы изменяете объект, который имеет параметр по умолчанию.
a_list = []
не изменяет прежний объект a_list
. Это не может; произвольные объекты не могут магически трансформироваться в пустые списки. a_list = []
означает, что "a_list
перестает ссылаться на то, о чем он ранее ссылался, и начинайте ссылаться на []
". Объект, ранее упомянутый , не изменяется.
Когда функция скомпилирована, и один из аргументов имеет значение по умолчанию, это значение - объект - получает запекается в функцию (которая тоже сама, объект!). Когда вы пишете код, который мутирует объект, объект мутирует. Если объект, на который ссылается, является объектом, запеченным в функции, он все еще мутирует.
Но вы не можете мутировать None
. Он неизменен.
Вы можете мутировать []
. Это список, и списки изменяемы. Добавление элемента в список мутирует список.
Ответ 3
Проблема существует только в том случае, если значение по умолчанию изменено, а None
- нет. Значение, которое хранится вместе с объектом функции, является значением по умолчанию. Когда функция вызывается, контекст функции инициализируется значением по умолчанию.
a_list = []
просто назначает новый объект имени a_list
в контексте текущего вызова функции. Он никоим образом не изменяет None
.
Ответ 4
Нет, в good_insert
a_list
не инициализируется только один раз.
Каждый раз, когда функция вызывается без указания аргумента a_list
, используется значение по умолчанию и используется и возвращается новый экземпляр list
, новый список не заменяет значение по умолчанию.