Подтвердить что ты не робот

Почему использование 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?

4b9b3361

Ответ 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, новый список не заменяет значение по умолчанию.