Вопрос по python – Почему использование None решает проблему непостоянных аргументов Python по умолчанию?

21

Я нахожусь в точке изучения Python, где я имею дело спроблема изменяемого аргумента по умолчанию.

def bad_append(new_item, a_list=[]):
    a_list.append(new_item)
    return a_list

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 было быstill быть инициализированным только один раз; Следовательноif утверждение будет верно только при первом вызове функции, то естьa_list будет только сбросить до[] при первом вызове, то есть все равно накопитnew_item значения и по-прежнему глючить.

Почему это не так? Какую концепцию мне не хватает? Какa_list стирать каждый разgood_append работает?

Ваш Ответ

4   ответа
15

Проблема существует только если по умолчаниюvalue изменчив, которыйNone не является. То, что хранится вместе с объектом функции, является значением по умолчанию. Когда функция вызывается, контекст функции инициализируется значением по умолчанию.

a_list = []

просто назначает новый объект имениa_list в контексте текущего вызова функции. Не меняетNone в любом случае.

У меня сложилось впечатление, что ментальная модель назначения и охвата ФП была ошибочной. Я переписал ответ, чтобы прояснить ситуацию.
Моя ментальная модель назначения была действительно ошибочной; на самом деле даже сейчас, когда я немного лучше понимаю проблему, она все еще может быть. Что я не понял, так это то, что когда вы делаетеa_list = None в определении функции, функция внутри имеетanother name for the same objectи что видимое имя параметра переназначается этому объекту в начале каждого вызова функции. 75th Trombone
4

good_insert a_list не инициализируется только один раз.

Каждый раз, когда функция вызывается без указанияa_list аргумент, используется значение по умолчанию и новый экземплярlist используется и возвращается, новый список не заменяет значение по умолчанию.

22

& Quot; инициализация & Quot; это не то, что происходит с переменными в Python, потому что переменные в Python - это просто имена. & Quot; инициализация & Quot; происходит только с объектами, и это делается с помощью класса.__init__ метод.

Когда ты пишешьa = 0это задание. Это говорит & quot;a будет ссылаться на объект, который описывается выражением0& Quot ;. Это не инициализация;a может назвать что-либо еще любого типа в любое более позднее время, и это происходит в результате назначения чего-то ещеa, Назначение - это просто назначение. Первый не особенный.

Когда ты пишешьdef good_append(new_item, a_list=None)это не "инициализация"a_list, Это настройка внутренней ссылки на объект, результат оценкиNoneтак что когдаgood_append вызывается без второго параметра, объект автоматически присваиваетсяa_list.

meaning a_list would only get reset to [] on the first invocation

Нет,a_list устанавливается на[] в любое времяa_list являетсяNone начать с. То есть когда либоNone передается явно или аргумент опущен.

Проблема с[] происходит потому, чтоthe expression [] толькоevaluated один раз в этом контексте. Когда функция скомпилирована,[] оценивается, аspecific Создается объект списка, который оказывается пустым для запуска, и этот объект используется по умолчанию.

How does a_list get wiped clean every time good_append runs?

Это не так. Это не обязательно.

Вы знаете, как описывается проблема с «изменяемыми аргументами по умолчанию»?

None не является изменчивым

Проблема возникает при измененииobject что параметр имеет по умолчанию.

a_list = [] не изменяет какой-либо объектa_list ранее упоминалось. Это не может; произвольные объекты не могут волшебным образом трансформироваться на месте в пустые списки.a_list = [] означает & quot;a_list должен прекратить ссылаться на то, что он ранее упоминал, и начать ссылаться на[]& Quot ;. Ранее упомянутыеobject без изменений.

Когда функция скомпилирована, и один из аргументов имеет значение по умолчанию, это значение -object - запекается в функцию (которая сама по себе является объектом!). Когда вы пишете код, который мутирует объект, объект мутирует. Если упоминаемый объект оказывается объектом, встроенным в функцию, он все еще видоизменяется.

Но вы не можете мутироватьNone, Это неизменно.

Вы можете изменить[], Это список, и списки изменчивы. Добавление элемента в список изменяет список.

Спасибо за отличный ответ. Я убиваю себя, пытаясь решить, пометить ли этот ответ или @ glglgl как правильный. Другой ответ содержит единственную вдохновляющую фразу, которая позволила мне понять ваш ответ; Ваш ответ в целом является более тщательным и понятным, но почему-то не дает легкого щелчка таким же образом. Если бы был какой-то способ поставить две зеленые галочки на вопросе, вы бы обязательно выбрали другую (и снова могли бы стать единственными, если я буду продолжать вафляться). 75th Trombone
12

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], Если этот объект будет впоследствии изменен, вы сохраните эту модификацию.

Напротив, назначениеinside функция всегда получает новый[], Любое изменение будет длиться до последней ссылки на это[] исчез; при следующем вызове функции, новый[] создано.

Опять же, это неправда, что[] получает один и тот же объект при каждом выполнении, но он (в случае аргумента по умолчанию) выполняется только один раз и затем сохраняется.

Большое спасибо; предложение & quot; что происходит при звонкеf() является неявнымx = f.func_defaults[0]& Quot; было жизненно важно для моего понимания. 75th Trombone
«Настолько, что я снова передумал и пометил это как правильный ответ. 75th Trombone

Похожие вопросы