Любой, кто возился с 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, без выявления ошибок?)
Редактировать :
Бачек сделал интересный пример. Вместе с большинством ваших комментариев и комментариев Утаала, в частности, я уточнил:
>>> def a():
... print("a executed")
... return []
...
>>>
>>> def b(x=a()):
... x.append(5)
... print(x)
...
a executed
>>> b()
[5]
>>> b()
[5, 5]
Мне кажется, что проектное решение было относительно того, куда поместить область параметров: внутри функции или «вместе» с ней?
Выполнение привязки внутри функции будет означать, что x
она эффективно привязана к указанному значению по умолчанию, когда функция вызывается, а не определяется, что может представлять собой серьезный недостаток: def
линия будет «гибридной» в том смысле, что часть привязки (из объект функции) будет происходить при определении, а часть (назначение параметров по умолчанию) - во время вызова функции.
Фактическое поведение является более последовательным: все, что в этой строке, оценивается при выполнении этой строки, то есть при определении функции.
источник
[5]
" Я новичок в Python, и я не ожидал бы этого, потому что, очевидноfoo([1])
, вернется[1, 5]
, а не[5]
. Что вы хотели сказать, так это то, что новичок ожидал, что функция, вызываемая без параметра , всегда будет возвращаться[5]
.Ответы:
На самом деле, это не недостаток дизайна, и это не из-за внутренних деталей или производительности.
Это происходит просто из того факта, что функции в Python являются первоклассными объектами, а не только частью кода.
Как только вы начинаете думать об этом, тогда это полностью имеет смысл: функция - это объект, оцениваемый по его определению; Параметры по умолчанию являются своего рода «данными члена», и поэтому их состояние может меняться от одного вызова к другому - точно так же, как и в любом другом объекте.
В любом случае, Effbot имеет очень хорошее объяснение причин такого поведения в значениях параметров по умолчанию в Python .
Я нашел это очень ясным, и я действительно предлагаю прочитать это для лучшего понимания того, как работают функциональные объекты.
источник
functions are objects
. В вашей парадигме предложение состоит в том, чтобы реализовать значения функций по умолчанию в качестве свойств, а не атрибутов.Предположим, у вас есть следующий код
Когда я вижу объявление о том, что есть, наименее удивительным является мысль о том, что, если первый параметр не задан, он будет равен кортежу.
("apples", "bananas", "loganberries")
Однако, как предполагается позже в коде, я делаю что-то вроде
тогда, если бы параметры по умолчанию были связаны при выполнении функции, а не объявлении функции, то я был бы изумлен (очень плохим способом), чтобы обнаружить, что фрукты были изменены. Это было бы более поразительным IMO, чем обнаружение того, что ваша
foo
функция выше меняла список.Настоящая проблема заключается в изменчивых переменных, и все языки имеют эту проблему в некоторой степени. Вот вопрос: предположим, в Java у меня есть следующий код:
Теперь, моя карта использует значение
StringBuffer
ключа, когда он был помещен в карту, или он хранит ключ по ссылке? В любом случае, кто-то удивлен; либо человек, который пытался вытащить объект,Map
используя значение, идентичное тому, с которым они его поместили, либо человек, который, кажется, не может извлечь свой объект, даже если ключ, который они используют, буквально является тем же объектом это использовалось для помещения его в карту (именно поэтому Python не позволяет использовать его изменяемые встроенные типы данных в качестве ключей словаря).Ваш пример - хороший случай, когда новички в Python будут удивлены и укушены. Но я бы сказал, что если бы мы «исправили» это, то это только создало бы другую ситуацию, когда их укусили бы, а эта была бы еще менее интуитивной. Более того, это всегда имеет место при работе с изменяемыми переменными; Вы всегда сталкиваетесь со случаями, когда кто-то может интуитивно ожидать того или иного поведения в зависимости от того, какой код он пишет.
Мне лично нравится текущий подход Python: аргументы функции по умолчанию оцениваются, когда функция определена, и этот объект всегда является значением по умолчанию. Я полагаю, что они могли бы использовать особый случай, используя пустой список, но такой особый случай вызвал бы еще большее удивление, не говоря уже о несовместимости в обратном направлении.
источник
("blueberries", "mangos")
.some_random_function()
дописываетfruits
вместо присвоения ему поведениеeat()
будет меняться. Так много для нынешнего замечательного дизайна. Если вы используете аргумент по умолчанию, на который ссылаются в другом месте, а затем изменяете ссылку извне функции, вы напрашиваетесь на неприятности. Настоящий WTF - это когда люди определяют свежий аргумент по умолчанию (литерал списка или вызов конструктора) и все еще получают бит.global
и переназначили кортеж - нет ничего удивительного, еслиeat
после этого все будет работать иначе.Соответствующая часть документации :
источник
function.data = []
) или, что еще лучше, создать объект.Я ничего не знаю о внутренней работе интерпретатора Python (и я не эксперт в компиляторах и интерпретаторах), поэтому не вините меня, если я предлагаю что-то неразумное или невозможное.
При условии, что объекты Python изменчивы, я думаю, что это следует учитывать при разработке аргументов по умолчанию. Когда вы создаете экземпляр списка:
вы ожидаете получить новый список, на который ссылается
a
.Почему
a=[]
всоздать новый список по определению функции, а не по вызову? Это как если бы вы спросили: «Если пользователь не предоставит аргумент, создайте новый список и используйте его так, как если бы он был создан вызывающей стороной». Я думаю, что это двусмысленно:
пользователь, хотите ли вы
a
по умолчанию установить дату и время, соответствующие определению или выполнениюx
? В этом случае, как и в предыдущем, я буду придерживаться того же поведения, как если бы аргумент по умолчанию «назначение» был первой инструкцией функции (datetime.now()
вызываемой при вызове функции). С другой стороны, если пользователь хочет отображать время определения, он может написать:Я знаю, я знаю: это закрытие. В качестве альтернативы Python может предоставить ключевое слово для принудительного связывания во время определения:
источник
class {}
интерпретировать содержимое блока как принадлежащее экземплярам :) Но когда классы являются первоклассными объектами, очевидно, что их содержимое (в памяти) естественным образом отражает их содержимое (в коде).Ну, причина в том, что привязки выполняются, когда выполняется код, и определение функции выполняется, ну ... когда функции определены.
Сравните это:
Этот код страдает от того же неожиданного случая. Бананы - это атрибут класса, и, следовательно, когда вы добавляете к нему что-то, он добавляется ко всем экземплярам этого класса. Причина точно такая же.
Это просто «Как это работает», и заставить его работать по-другому в случае функции, вероятно, будет сложно, а в случае класса, вероятно, невозможно, или, по крайней мере, сильно замедлить создание экземпляров объекта, так как вам придется держать код класса вокруг и выполнить его при создании объектов.
Да, это неожиданно. Но как только копейка падает, она отлично вписывается в общую работу Python. На самом деле, это хорошее учебное пособие, и как только вы поймете, почему это происходит, вы станете гораздо лучше питонить.
Тем не менее, это должно быть заметно в любом хорошем учебнике по Python. Потому что, как вы упоминаете, все сталкиваются с этой проблемой рано или поздно.
источник
property
s - которые на самом деле являются функциями уровня класса, которые действуют как обычные атрибуты, но сохраняют атрибут в экземпляре вместо класса (используя,self.attribute = value
как сказал Леннарт).Почему бы тебе не заняться самоанализом?
Я действительно удивлен, что никто не выполнил проницательный самоанализ, предложенный Python (
2
и3
применить) в отношении вызываемых.Учитывая простую маленькую функцию,
func
определенную как:Когда Python встречает его, первое, что он сделает, это скомпилирует его, чтобы создать
code
объект для этой функции. Пока этот шаг компиляции сделан, Python оценивает * и затем сохраняет параметры по умолчанию (здесь пустой список[]
) в самом объекте функции . Как отмечается в верхнем ответе: списокa
теперь можно считать членом функцииfunc
.Итак, давайте сделаем некоторый самоанализ, до и после, чтобы проверить, как список расширяется внутри объекта функции. Я использую
Python 3.x
для этого, для Python 2 применяется то же самое (используйте__defaults__
илиfunc_defaults
в Python 2; да, два имени для одной и той же вещи).Функция перед выполнением:
После того, как Python выполнит это определение, он возьмет любые заданные по умолчанию параметры (
a = []
здесь) и поместит их в__defaults__
атрибут для объекта функции (соответствующий раздел: Callables):Итак, пустой список как одна запись
__defaults__
, как и ожидалось.Функция после выполнения:
Давайте теперь выполним эту функцию:
Теперь давайте посмотрим на них
__defaults__
снова:Изумленные? Значение внутри объекта меняется! Последовательные вызовы функции теперь просто добавляются к этому внедренному
list
объекту:Итак, у вас есть причина, по которой возникает этот «недостаток» , потому что аргументы по умолчанию являются частью объекта функции. Здесь нет ничего странного, все это немного удивляет.
Распространенным решением для борьбы с этим является использование
None
по умолчанию, а затем инициализация в теле функции:Так как тело функции выполняется заново каждый раз, вы всегда получаете новый новый пустой список, если аргумент не был передан
a
.Для дальнейшей проверки того, что список в списке
__defaults__
совпадает с используемым в функции,func
вы можете просто изменить свою функцию, чтобы она возвращалаid
список,a
используемый внутри тела функции. Затем сравните его со списком в__defaults__
(position[0]
in__defaults__
), и вы увидите, как они действительно относятся к одному и тому же экземпляру списка:Все с силой самоанализа!
* Чтобы убедиться, что Python оценивает аргументы по умолчанию во время компиляции функции, попробуйте выполнить следующее:
как вы заметите,
input()
вызывается до того, как будет создан процесс построения функции и привязки ее к имениbar
.источник
id(...)
для последней проверки, илиis
оператор ответил бы на тот же вопрос?is
будет в порядке, я просто использовал,id(val)
потому что думаю, что он может быть более интуитивным.None
по умолчанию серьезно ограничивает полезность__defaults__
самоанализа, поэтому я не думаю, что это работает как защита от__defaults__
работы так, как работает. Ленивая оценка сделала бы больше, чтобы сохранить функциональные значения по умолчанию полезными с обеих сторон.Раньше я думал, что создание объектов во время выполнения будет лучшим подходом. Сейчас я менее уверен, так как вы теряете некоторые полезные функции, хотя это может стоить того, чтобы просто не допустить путаницы новичка. Недостатки этого:
1. Производительность
Если используется оценка во время вызова, то дорогая функция вызывается каждый раз, когда ваша функция используется без аргумента. Вы либо платите дорогую цену за каждый вызов, либо вам необходимо вручную кэшировать это значение, загрязняя пространство имен и добавляя многословность.
2. Принудительное связывание параметров
Полезный трюк - привязать параметры лямбды к текущей привязке переменной при создании лямбды. Например:
Это возвращает список функций, которые возвращают 0,1,2,3 ... соответственно. Если поведение будет изменено, они вместо этого будут привязаны
i
к значению времени вызова i, так что вы получите список функций, которые все вернули9
.Единственный способ реализовать это иначе - создать дальнейшее замыкание с привязкой i, т.е.
3. Самоанализ
Рассмотрим код:
Мы можем получить информацию об аргументах и значениях по умолчанию, используя
inspect
модуль, которыйЭта информация очень полезна для таких вещей, как генерация документов, метапрограммирование, декораторы и т. Д.
Теперь предположим, что поведение значений по умолчанию можно изменить так, чтобы это было эквивалентно:
Тем не менее, мы потеряли способность к самоанализу, и посмотреть , какие аргументы по умолчанию являются . Поскольку объекты не были построены, мы никогда не сможем их заполучить, фактически не вызвав функцию. Лучшее, что мы можем сделать, это сохранить исходный код и вернуть его в виде строки.
источник
5 очков в защиту Python
Простота : поведение простое в следующем смысле: большинство людей попадают в эту ловушку только один раз, а не несколько раз.
Согласованность : Python всегда передает объекты, а не имена. Параметр по умолчанию, очевидно, является частью заголовка функции (а не тела функции). Поэтому его следует оценивать во время загрузки модуля (и только во время загрузки модуля, если он не вложен), а не во время вызова функции.
Полезность : как Фредерик Лунд указывает в своем объяснении «Значения параметров по умолчанию в Python» , текущее поведение может быть весьма полезным для продвинутого программирования. (Используйте экономно.)
Достаточная документация : в самой базовой документации Python, учебнике, проблема громко объявлена как «Важное предупреждение» в первом подразделе Раздела «Подробнее об определении функций» . В предупреждении даже используется жирный шрифт, который редко применяется за пределами заголовков. RTFM: прочитайте прекрасное руководство.
Мета-обучение : Попадание в ловушку на самом деле очень полезный момент (по крайней мере, если вы учитесь размышлять), потому что впоследствии вы лучше поймете пункт «Согласованность» выше, и это многому вас научит в Python.
источник
Такое поведение легко объяснить:
Так:
a
не изменяется - каждый вызов присваивания создает новый объект int - печатается новый объектb
не изменяется - новый массив строится из значения по умолчанию и печатаетсяc
изменения - операция выполняется над тем же объектом - и она печатаетсяисточник
__iadd__
, но он не работает с int. Конечно. :-)То, что вы спрашиваете, почему это:
внутренне не эквивалентно этому:
за исключением случая явного вызова func (None, None), который мы проигнорируем.
Другими словами, вместо оценки параметров по умолчанию, почему бы не сохранить каждый из них и не оценить их при вызове функции?
Один ответ, вероятно, прямо здесь - он фактически превратит каждую функцию с параметрами по умолчанию в замыкание. Даже если все это скрыто в интерпретаторе, а не в полномасштабном закрытии, данные должны где-то храниться. Это будет медленнее и использовать больше памяти.
источник
1) Так называемая проблема «изменяемого аргумента по умолчанию» в целом является особым примером, демонстрирующим, что:
«Все функции с этой проблемой страдают также от аналогичной проблемы побочных эффектов для фактического параметра ».
то есть против правил функционального программирования, обычно нежелательны и должны быть исправлены вместе.
Пример:
Решение : а копия
Абсолютно безопасное решение является
copy
илиdeepcopy
входной объект первым , а затем делать все , что с копией.Многие встроенные изменяемые типы имеют метод копирования, например
some_dict.copy()
или,some_set.copy()
или могут быть скопированы так же, какsomelist[:]
илиlist(some_list)
. Каждый объект также может быть скопированcopy.copy(any_object)
или более тщательноcopy.deepcopy()
(последний полезен, если изменяемый объект составлен из изменяемых объектов). Некоторые объекты в основном основаны на побочных эффектах, таких как «файловый» объект, и не могут быть воспроизведены путем копирования. копированиеПример задачи для аналогичного вопроса SO
Он не должен быть сохранен ни в каком открытом атрибуте экземпляра, возвращаемого этой функцией. (Предполагая, что частные атрибуты экземпляра не должны изменяться извне этого класса или подклассов по соглашению. Т.е.
_var1
является приватным атрибутом)Вывод:
Объекты входных параметров не должны быть изменены на месте (видоизменены) и не должны быть связаны в объект, возвращаемый функцией. (Если мы предпочитаем программирование без побочных эффектов, что настоятельно рекомендуется. Смотрите вики о "побочном эффекте" (первые два абзаца являются подходящими в этом контексте.).)
2)
Только если побочный эффект для фактического параметра требуется, но нежелателен для параметра по умолчанию, тогда полезным решением является
def ...(var1=None):
if var1 is None:
var1 = []
Подробнее.3) В некоторых случаях полезно изменяемое поведение параметров по умолчанию .
источник
def f( a = None )
конструкция рекомендуется, когда вы действительно имеете в виду что-то еще. Копирование - это нормально, потому что вы не должны изменять аргументы. И когда вы это делаетеif a is None: a = [1, 2, 3]
, вы все равно копируете список.Это на самом деле не имеет ничего общего со значениями по умолчанию, за исключением того, что часто возникает неожиданное поведение при написании функций с изменяемыми значениями по умолчанию.
В этом коде нет значений по умолчанию, но вы получите точно такую же проблему.
Проблема в том, что
foo
это изменении изменяемого переменная передается из вызывающего абонента, когда абонент не ожидает. Код, как это было бы хорошо, если бы функция была вызвана что-то вродеappend_5
; тогда вызывающая сторона будет вызывать функцию, чтобы изменить передаваемое значение, и поведение будет ожидаемым. Но такая функция вряд ли будет принимать аргумент по умолчанию и, вероятно, не будет возвращать список (поскольку вызывающая сторона уже имеет ссылку на этот список; тот, который она только что передала).Ваш оригинал
foo
с аргументом по умолчанию не должен изменятьсяa
, был ли он передан явно или получил значение по умолчанию. Ваш код должен оставлять изменяемые аргументы в покое, если из контекста / имени / документации не ясно, что аргументы должны быть изменены. Использование изменяемых значений, передаваемых в качестве аргументов в качестве локальных временных файлов, является крайне плохой идеей, независимо от того, находимся ли мы в Python или нет, и задействованы ли аргументы по умолчанию или нет.Если вам нужно деструктивно манипулировать локальным временным в процессе вычисления чего-либо, и вам нужно начать манипулирование со значения аргумента, вам нужно сделать копию.
источник
append
чтобы изменитьa
"на месте"). То, что изменяемая по умолчанию переменная не восстанавливается при каждом вызове, является "неожиданным" битом ... по крайней мере для меня. :)cache={}
. Тем не менее, я подозреваю, что возникает «наименьшее удивление», когда вы не ожидаете (или не хотите) функцию, которую вы вызываете, чтобы изменить аргумент.cache={}
в это для полноты.None
и присвоение истинного значения по умолчанию, если arg isNone
, не решает эту проблему (по этой причине я считаю это анти-паттерном). Если вы исправите другую ошибку, избегая изменяющихся значений аргументов, независимо от того, имеют ли они значения по умолчанию, тогда вы никогда не заметите и не позаботитесь об этом «удивительном» поведении.Тема уже занята, но из того, что я прочитал здесь, следующее помогло мне понять, как это работает внутри:
источник
a = a + [1]
перегрузкиa
... рассмотрите возможность изменить егоb = a + [1] ; print id(b)
и добавить строкуa.append(2)
. Это сделает более очевидным, что+
в двух списках всегда создается новый список (назначенныйb
), в то время как измененныйa
может иметь то же самоеid(a)
.Это оптимизация производительности. В результате этой функциональности, какой из этих двух вызовов функций вы считаете более быстрым?
Я дам вам подсказку. Вот разборка (см. Http://docs.python.org/library/dis.html ):
#
1#
2Как вы можете видеть, это выигрыш в производительности при использовании неизменяемых аргументов по умолчанию. Это может иметь значение, если это часто вызываемая функция или для создания аргумента по умолчанию требуется много времени. Также имейте в виду, что Python не C. В C у вас есть константы, которые в значительной степени свободны. В Python у вас нет этой выгоды.
источник
Python: изменяемый аргумент по умолчанию
Аргументы по умолчанию оцениваются во время компиляции функции в объект функции. При использовании этой функции несколько раз этой функцией они являются и остаются одним и тем же объектом.
Когда они являются изменяемыми, когда мутируют (например, путем добавления элемента к нему), они остаются мутированными при последовательных вызовах.
Они остаются мутированными, потому что они - один и тот же объект каждый раз.
Эквивалентный код:
Так как список привязан к функции, когда объект функции компилируется и создается, это:
почти точно эквивалентно этому:
демонстрация
Вот демонстрация - вы можете проверить, что это один и тот же объект каждый раз, когда на них ссылается
example.py
и запустить его с
python example.py
:Это нарушает принцип «Наименьшего удивления»?
Этот порядок выполнения часто сбивает с толку новых пользователей Python. Если вы понимаете модель исполнения Python, то она становится вполне ожидаемой.
Обычная инструкция для новых пользователей Python:
Но именно поэтому обычная инструкция для новых пользователей состоит в том, чтобы вместо этого создавать их аргументы по умолчанию:
При этом используется синглтон None в качестве сторожевого объекта, чтобы сообщить функции, получили ли мы аргумент, отличный от значения по умолчанию. Если мы не получим аргумента, то на самом деле мы хотим использовать новый пустой список,
[]
по умолчанию.Как сказано в учебном разделе о потоке управления :
источник
Самый короткий ответ, вероятно, будет «определение - исполнение», поэтому весь аргумент не имеет строгого смысла. В качестве более надуманного примера вы можете привести это:
Надеюсь, этого достаточно, чтобы показать, что не выполняются выражения аргумента по умолчанию во время выполнения
def
оператора нелегко или не имеет смысла, или и то, и другое.Я согласен с этим, когда вы пытаетесь использовать конструкторы по умолчанию.
источник
Простой обходной путь с использованием None
источник
Такое поведение не удивительно, если принять во внимание следующее:
Роль (2) широко освещалась в этой теме. (1) , вероятно, является фактором, вызывающим удивление, так как это поведение не является «интуитивным» при переходе с других языков.
(1) описано в уроке по Python для классов . При попытке присвоить значение атрибуту класса только для чтения:
Вернитесь к исходному примеру и рассмотрите вышеупомянутые пункты:
Здесь
foo
объект иa
является атрибутомfoo
(доступно вfoo.func_defs[0]
). Посколькуa
это список,a
он изменчив и, следовательно, является атрибутом чтения-записиfoo
. Он инициализируется пустым списком, как указано в сигнатуре, когда создается экземпляр функции, и доступен для чтения и записи, пока существует объект функции.Вызов
foo
без переопределения значения по умолчанию использует значение по умолчанию изfoo.func_defs
. В этом случаеfoo.func_defs[0]
используется дляa
внутри области кода объекта функции. Изменения кa
изменениюfoo.func_defs[0]
, которое является частьюfoo
объекта и сохраняется между выполнением кода вfoo
.Теперь сравните это с примером из документации по эмуляции поведения аргументов по умолчанию в других языках , так что значения сигнатур функции по умолчанию используются каждый раз, когда функция выполняется:
Принимая во внимание (1) и (2) , можно понять, почему это приводит к желаемому поведению:
foo
объект функции создается ,foo.func_defs[0]
устанавливается наNone
неизменяемый объект.L
вызова функции),foo.func_defs[0]
(None
) доступно в локальной области какL
.L = []
назначении не может быть успешноfoo.func_defs[0]
, потому что этот атрибут только для чтения.L
, создается в локальной области и используется для оставшейся части вызова функции.foo.func_defs[0]
Таким образом, остается неизменным для будущих вызововfoo
.источник
Я собираюсь продемонстрировать альтернативную структуру для передачи значения списка по умолчанию в функцию (она одинаково хорошо работает со словарями).
Как и другие подробно прокомментировали, параметр списка привязан к функции, когда он определен, а не когда он выполняется. Поскольку списки и словари являются изменчивыми, любое изменение этого параметра повлияет на другие вызовы этой функции. В результате последующие вызовы функции получат этот общий список, который мог быть изменен любыми другими вызовами функции. Хуже того, два параметра используют параметр общей функции этой функции одновременно, не обращая внимания на изменения, сделанные другой.
Неправильный метод (вероятно ...) :
Вы можете проверить, что это один и тот же объект, используя
id
:Пер Бретт Слаткин "Эффективный Python: 59 конкретных способов написать лучший Python", пункт 20. Использование
None
и строки документации для указания динамических аргументов по умолчанию (стр. 48)Эта реализация гарантирует, что каждый вызов функции либо получает список по умолчанию, либо список, переданный функции.
Предпочитаемый метод :
Для «неправильного метода» могут быть допустимые варианты использования, когда программист намеревался предоставить общий доступ к параметру списка по умолчанию, но это скорее исключение, чем правило.
источник
Решения здесь:
None
качестве значения по умолчанию (или одноразового номераobject
) и включите его, чтобы создавать значения во время выполнения; илиlambda
качестве параметра по умолчанию и вызывайте его в блоке try, чтобы получить значение по умолчанию (именно для этого предназначена лямбда-абстракция).Второй вариант хорош, потому что пользователи функции могут передать вызываемый объект, который может уже существовать (например, a
type
)источник
Когда мы делаем это:
... мы относим аргумент
a
в неназванный списку, если вызывающему абонент не передает значение а.Чтобы упростить обсуждение, давайте временно дадим неназванному списку имя. Как насчет
pavlo
?В любое время, если вызывающий абонент не сообщает нам, что это
a
такое, мы используем его повторноpavlo
.Если он
pavlo
является изменяемым (модифицируемым) и вfoo
конечном итоге модифицирует его, то эффект, который мы заметим в следующий раз,foo
вызывается без указанияa
.Итак, вот что вы видите (помните,
pavlo
инициализируется как []):Сейчас же,
pavlo
есть [5].Вызов
foo()
снова изменяетpavlo
снова:Указание
a
при звонкеfoo()
гарантирует,pavlo
что не трогается.Итак,
pavlo
все еще[5, 5]
.источник
Иногда я использую это поведение в качестве альтернативы следующей схеме:
Если
singleton
используется толькоuse_singleton
мне, мне нравится следующий шаблон в качестве замены:Я использовал это для создания экземпляров клиентских классов, которые обращаются к внешним ресурсам, а также для создания диктовок или списков для запоминания.
Поскольку я не думаю, что эта модель хорошо известна, я добавлю небольшой комментарий, чтобы избежать недопонимания в будущем.
источник
_make_singleton
во время def в примере аргумента по умолчанию, но во время вызова в глобальном примере. Истинная подстановка будет использовать некое изменяемое поле для значения аргумента по умолчанию, но добавление аргумента дает возможность передавать альтернативные значения.Вы можете обойти это, заменив объект (и, следовательно, связать с областью действия):
Уродливо, но это работает.
источник
Это может быть правдой, что:
полностью соответствует обеим вышеперечисленным функциям, и все же следует подчеркнуть следующее:
Другие ответы или, по крайней мере, некоторые из них либо дают очки 1 и 2, но не 3, либо дают точку 3 и баллы приглушения 1 и 2. Но все три верны.
Возможно, это правда, что переключение лошадей в среднем потоке здесь потребовало бы значительных поломок, и что может быть больше проблем, связанных с изменением Python для интуитивной обработки открывающего фрагмента Стефано. И это может быть правдой, что тот, кто хорошо знал внутренности Python, мог объяснить минное поле последствий. Однако,
Существующее поведение не Pythonic, и Python успешен, потому что очень мало о языке нарушает принцип наименьшего удивления где-либо рядомэто плохо. Это реальная проблема, было бы разумно искоренить это. Это недостаток дизайна. Если вы гораздо лучше понимаете язык, пытаясь отследить поведение, я могу сказать, что C ++ делает все это и даже больше; Вы многому научитесь, перемещаясь, например, по тонким ошибкам указателя. Но это не Pythonic: люди, которые заботятся о Python достаточно, чтобы выдержать это поведение, являются людьми, которых привлекает язык, потому что у Python гораздо меньше сюрпризов, чем у другого языка. Дейблеры и любопытные становятся Pythonistas, когда они удивляются тому, как мало времени требуется, чтобы что-то заработало - не из-за дизайна, я имею в виду скрытую логическую головоломку, которая прорезает интуицию программистов, которые тянутся к Python потому что это просто работает .
источник
x=[]
означает «создайте пустой объект списка и привяжите к нему имя« x »». Таким образом, вdef f(x=[])
, пустой список также создается. Он не всегда привязан к x, поэтому он привязан к суррогату по умолчанию. Позже, когда вызывается f (), значение по умолчанию вынимается и связывается с x. Поскольку это был сам пустой список, который был спрятан, этот же список - единственное, что можно связать с x, независимо от того, было ли внутри что-то застряло или нет. Как может быть иначе?Это не недостаток дизайна . Любой, кто спотыкается об этом, делает что-то не так.
Я вижу 3 случая, когда вы можете столкнуться с этой проблемой:
cache={}
, и вы не должны вызывать функцию с фактическим аргументом вообще.Пример в вопросе может попасть в категорию 1 или 3. Странно, что он и изменяет переданный список, и возвращает его; Вы должны выбрать один или другой.
источник
cache={}
Картина действительно интервью-единственное решение, в реальном коде вы , вероятно , хотите@lru_cache
!Этот "жучок" дал мне много сверхурочной работы! Но я начинаю видеть потенциальное использование этого (но я хотел бы, чтобы это было во время выполнения, все же)
Я дам вам то, что я считаю полезным примером.
печатает следующее
источник
Просто измените функцию на:
источник
Я думаю, что ответ на этот вопрос заключается в том, как python передает данные параметру (передача по значению или по ссылке), а не изменчивости или как python обрабатывает оператор «def».
Краткое введение. Во-первых, в Python есть два типа данных: один простой элементарный тип данных, такой как числа, а другой тип данных - объекты. Во-вторых, при передаче данных в параметры python передает элементарный тип данных по значению, т.е. создает локальную копию значения в локальной переменной, но передает объект по ссылке, то есть указатели на объект.
Признавая два вышеизложенных момента, давайте объясним, что случилось с кодом Python. Это происходит только из-за передачи по ссылке для объектов, но не имеет ничего общего с изменяемым / неизменным, или, возможно, с тем фактом, что оператор «def» выполняется только один раз, когда он определен.
[] является объектом, поэтому python передает ссылку на []
a
, т. е.a
является только указателем на [], который находится в памяти как объект. Существует только одна копия [] с множеством ссылок на нее. Для первого foo () список [] изменяется на 1 методом добавления. Но обратите внимание, что существует только одна копия объекта списка, и этот объект теперь становится 1 . При запуске второй функции foo () неверно то, что говорит веб-страница effbot (элементы больше не оцениваются).a
оценивается как объект списка, хотя теперь содержимое объекта равно 1 . Это эффект передачи по ссылке! Результат foo (3) может быть легко получен таким же образом.Для дальнейшей проверки моего ответа давайте рассмотрим два дополнительных кода.
====== № 2 ========
[]
является объектом, так и естьNone
(первый изменчив, а второй неизменен. Но изменчивость не имеет ничего общего с вопросом). Никого нет где-то в космосе, но мы знаем, что это там, и там есть только одна копия Никого. Таким образом, каждый раз, когда вызывается foo, элементы оцениваются (в отличие от некоторого ответа, что он оценивается только один раз) как None, чтобы быть понятным, ссылка (или адрес) None. Затем в foo элемент изменяется на [], т. Е. Указывает на другой объект с другим адресом.====== № 3 =======
Вызов foo (1) заставляет элементы указывать на объект списка [] с адресом, скажем, 11111111. Содержимое списка изменяется на 1 в функции foo в дальнейшем, но адрес не изменяется, все же 11111111 Затем приходит foo (2, []). Хотя [] в foo (2, []) имеет то же содержимое, что и параметр по умолчанию [] при вызове foo (1), их адреса разные! Поскольку мы предоставляем параметр явно,
items
он должен взять адрес этого нового[]
, скажем, 2222222, и вернуть его после внесения некоторых изменений. Теперь foo (3) выполняется. так как толькоx
При условии, элементы должны снова принять значение по умолчанию. Какое значение по умолчанию? Он устанавливается при определении функции foo: объекта списка, расположенного в 11111111. Таким образом, элементы оцениваются как адрес 11111111, имеющий элемент 1. Список, расположенный в 2222222, также содержит один элемент 2, но он не указывается элементами Больше. Следовательно, приложение из 3 составитitems
[1,3].Из приведенных выше объяснений видно, что рекомендованная в принятом ответе веб-страница effbot не дала соответствующего ответа на этот вопрос. Более того, я думаю, что точка на веб-странице effbot неверна. Я думаю, что код относительно UI.Button правильный:
Каждая кнопка может содержать отдельную функцию обратного вызова, которая будет отображать различное значение
i
. Я могу предоставить пример, чтобы показать это:Если мы выполним,
x[7]()
мы получим 7, как и ожидалось, иx[9]()
даст 9, другое значениеi
.источник
x[7]()
это так9
.TLDR: значения по умолчанию для определения времени согласованы и строго более выразительны.
Определение функции влияет на две области: область определения, содержащую функцию, и область выполнения, содержащуюся в функции. Хотя довольно ясно, как блоки отображаются в области видимости, вопрос в том, где
def <name>(<args=defaults>):
:def name
Часть должна оценить в определяющей сфере - мы хотимname
быть доступны там, в конце концов. Оценка функции только внутри себя сделает ее недоступной.Поскольку
parameter
это постоянное имя, мы можем «оценить» его одновременно сdef name
. Это также имеет то преимущество, что создает функцию с известной сигнатуройname(parameter=...):
вместо простойname(...):
.Теперь, когда оценивать
default
?Последовательность уже говорит «при определении»: все остальное
def <name>(<args=defaults>):
лучше всего оценивать и при определении. Задержка его частей была бы удивительным выбором.Оба варианта также не эквивалентны: если
default
вычисляется во время определения, это все равно может повлиять на время выполнения. Еслиdefault
оценивается во время выполнения, это не может повлиять на время определения. Выбор «при определении» позволяет выразить оба случая, а при выборе «при исполнении» можно выразить только один:источник
def <name>(<args=defaults>):
лучше всего оценивать и при определении». Я не думаю, что вывод следует из предпосылки. Тот факт, что две вещи находятся в одной строке, не означает, что они должны оцениваться в одной и той же области.default
это другая вещь, чем остальная часть строки: это выражение. Оценка выражения - это совсем другой процесс, нежели определение функции.def
) или выражением (lambda
), создание функции означает оценку, особенно ее сигнатуры. И значения по умолчанию являются частью сигнатуры функции. Это не означает, что значения по умолчанию должны быть оценены немедленно - подсказки типа могут не, например. Но это, безусловно, говорит о том, что они должны делать, если нет веских причин не делать этого.Любой другой ответ объясняет, почему это на самом деле хорошее и желаемое поведение, или почему вам все равно это не нужно. Мое предназначение для тех упрямых, которые хотят реализовать свое право подчинить язык своей воле, а не наоборот.
Мы «исправим» это поведение с помощью декоратора, который будет копировать значение по умолчанию вместо повторного использования одного и того же экземпляра для каждого позиционного аргумента, оставленного со значением по умолчанию.
Теперь давайте переопределим нашу функцию с помощью этого декоратора:
Это особенно удобно для функций, которые принимают несколько аргументов. Для сравнения:
с
Важно отметить, что указанное выше решение не работает, если вы попытаетесь использовать аргументы ключевых слов, например:
Декоратор может быть настроен, чтобы учесть это, но мы оставляем это как упражнение для читателя;)
источник