Если вы вдруг столкнулись с этой ошибкой, произнесите в рабочей среде и ничего не изменилось, попробуйте следующие 4 элемента в следующем порядке, чтобы узнать, исправляется ли это.
На самом деле, это не ошибка дизайна, и это происходит не из-за внутренних компонентов, а из-за производительности. Это происходит просто из-за того, что функции в Python являются первоклассными объектами, а не только частью кода.
Как только вы додумаетесь до этого, тогда это полностью имеет смысл: функция - объект, оцениваемый по его определению; параметры по умолчанию являются «данными-членами», и поэтому их состояние может меняться от одного вызова к другому - точно так же, как и к любому другому объекту.
В любом случае Effbot имеет очень хорошее объяснение причин это поведение в Значения параметров по умолчанию в Python . Я нашел это очень ясным, и я действительно предлагаю прочитать его, чтобы лучше узнать, как работают объекты функций.
Я ничего не знаю о внутренних интерпретаторах Python-интерпретатора (и я тоже не являюсь экспертом в компиляторах и интерпретаторах), поэтому не обвиняйте меня, если я предлагаю что-либо недоступное или невозможное.
При условии, что python объекты меняются. Я думаю, что это следует учитывать при разработке аргументов аргументов по умолчанию. Когда вы создаете экземпляр списка:
a = []
вы ожидаете получить новый список, на который ссылается a .
Почему a = [] в
def x(a=[]):
создать новый список по определению функции, а не по вызову? Это точно так же, как вы спрашиваете: «Если пользователь не предоставляет аргумент, тогда создает экземпляр нового списка и использует его, как если бы он был создан вызывающим». Я думаю, что это неоднозначно:
def x(a=datetime.datetime.now()):
пользователь, вы хотите a по умолчанию использовать дату и время, соответствующее тому, когда вы определяете или выполняете x ? В этом случае, как и в предыдущем, я буду придерживаться такого же поведения, как если бы аргумент по умолчанию «назначение» был первой инструкцией функции (datetime.now (), вызванной вызовом функции). С другой стороны, если пользователь хотел отобразить время отображения, он мог бы написать:
b = datetime.datetime.now()
def x(a=b):
Я знаю, я знаю: это закрытие. В качестве альтернативы Python может предоставить ключевое слово для привязки определения времени:
def x(static a=b):
class {}
интерпретироваться как принадлежащий экземплярам i> :). Но когда классы являются первоклассными объектами, очевидно, что естественным является их содержимое (в памяти), чтобы отразить их содержимое (в коде).
– Karl Knechtel
22 July 2011 в 20:55
Аргументы по умолчанию получают оценку во время компиляции функции в объект функции. При использовании этой функции, несколько раз с помощью этой функции, они являются и остаются одним и тем же объектом.
Когда они изменяются, когда они мутируются (например, добавляя к нему элемент), они остаются мутированными по последовательным вызовам.
Они остаются мутированными, потому что каждый раз они являются одним и тем же объектом .
Поскольку список связан с функцией, когда объект функции компилируется и инстанцируется, это:
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, это становится вполне ожидаемым.
Но поэтому обычная инструкция для новых пользователей заключается в том, чтобы вместо этого создать свои аргументы по умолчанию:
def example_function_2(default_kwarg=None):
if default_kwarg is None:
default_kwarg = []
Это использует None singleton как объект-дозор, чтобы сообщить функции, есть ли у нас аргумент, отличный от значения по умолчанию. Если мы не получаем никакого аргумента, то мы действительно хотим использовать новый пустой список []
в качестве значения по умолчанию.
Как говорится в разделе в разделе управления потоком :
Если вы не хотите, чтобы по умолчанию были разделены между последующими вызовами, вы можете написать такую функцию, как это:
def f(a, L=None): if L is None: L = [] L.append(a) return L
Я собираюсь продемонстрировать альтернативную структуру, чтобы передать значение списка по умолчанию функции (она одинаково хорошо работает со словарями).
Поскольку другие подробно комментируют, параметр списка привязан к функции, когда она определена, а не когда она выполняется. Поскольку списки и словари изменяемы, любое изменение этого параметра влияет на другие вызовы этой функции. В результате последующие вызовы функции получат этот общий список, который может быть изменен любыми другими вызовами функции. Хуже того, два параметра используют общий параметр этой функции, в то же время не обращая внимания на изменения, сделанные другим.
Неверный метод (возможно ...):
def foo(list_arg=[5]):
return list_arg
a = foo()
a.append(6)
>>> a
[5, 6]
b = foo()
b.append(7)
# The value of 6 appended to variable 'a' is now part of the list held by 'b'.
>>> b
[5, 6, 7]
# Although 'a' is expecting to receive 6 (the last element it appended to the list),
# it actually receives the last element appended to the shared list.
# It thus receives the value 7 previously appended by 'b'.
>>> a.pop()
7
Вы можете убедиться, что это один и тот же объект, используя id
:
>>> id(a)
5347866528
>>> id(b)
5347866528
Per Brett Slatkin's «Эффективный Python: 59 конкретных способов записи лучшего Python», Пункт 20: Использование None
и Docstrings для указания динамических аргументов по умолчанию (стр. 48)
Соглашение о достижении желаемого результата в Python заключается в предоставлении значения по умолчанию
blockquote>None
и документируйте фактическое поведение в docstring.Эта реализация гарантирует, что каждый вызов функции либо получает список по умолчанию, либо список, переданный функции.
Предпочтительный Метод:
def foo(list_arg=None): """ :param list_arg: A list of input values. If none provided, used a list with a default value of 5. """ if not list_arg: list_arg = [5] return list_arg a = foo() a.append(6) >>> a [5, 6] b = foo() b.append(7) >>> b [5, 7] c = foo([10]) c.append(11) >>> c [10, 11]
В «Неправильном методе» могут быть законные варианты использования, в соответствии с которыми программист предполагает, что параметр списка по умолчанию должен быть общим, но это скорее исключение, чем правило.
Самый короткий ответ, вероятно, будет «определение - исполнение», поэтому весь аргумент не имеет строгой точки зрения. В качестве более надуманного примера вы можете привести следующее:
def a(): return []
def b(x=a()):
print x
. Надеюсь, этого достаточно, чтобы показать, что не выполнять выражения аргументов по умолчанию во время выполнения инструкции def
не просто или не работает, т. е. смысл, или и то, и другое.
Я согласен с тем, что при попытке использовать конструкторы по умолчанию, это будет gotcha.
Это фактически не имеет ничего общего с значениями по умолчанию, кроме того, что часто возникает неожиданное поведение при записи функций с изменяемыми значениями по умолчанию.
>>> def foo(a):
a.append(5)
print a
>>> a = [5]
>>> foo(a)
[5, 5]
>>> foo(a)
[5, 5, 5]
>>> foo(a)
[5, 5, 5, 5]
>>> foo(a)
[5, 5, 5, 5, 5]
В этом нет значений по умолчанию код, но вы получаете точно такую же проблему.
Проблема в том, что foo
является изменением изменчивой переменной, переданной от вызывающего, когда вызывающий объект не ожидает этого , Код, подобный этому, был бы хорош, если бы функция была вызвана как-то вроде append_5
; то вызывающий абонент будет вызывать функцию, чтобы изменить значение, которое они передают, и поведение будет ожидаться. Но такая функция вряд ли примет аргумент по умолчанию и, вероятно, не вернет список (поскольку у вызывающего уже есть ссылка на этот список, тот, который он только что передал).
Ваш оригинал foo
с аргументом по умолчанию не должен изменять a
, был ли он явно передан или получил значение по умолчанию. Ваш код должен оставлять изменчивые аргументы отдельно, если из контекста / имени / документации не ясно, что аргументы должны быть изменены. Использование измененных значений, передаваемых в качестве аргументов, таких как локальные временные файлы, является крайне плохой идеей, независимо от того, находимся ли мы на Python или нет, и есть ли задействованные аргументы по умолчанию.
Если вам нужно разрушить локальное временное в процессе вычисления чего-то, и вам нужно начать свою манипуляцию из значения аргумента, вам нужно сделать копию.
append
изменится a
«на месте»). То, что измененный по умолчанию не восстанавливается повторно при каждом вызове , является «неожиданным». бит ... по крайней мере для меня. :)
– Andy Hayden
24 August 2012 в 13:27
cache={}
. Однако я подозреваю, что это «наименьшее удивление» появляется, когда вы не i> ожидаете (или хотите) функцию, которую вы вызываете, для изменения аргумента.
– Andy Hayden
17 October 2017 в 17:56
cache={}
в него для полноты.
– Mark Ransom
17 October 2017 в 18:02
None
и присвоение реального значения по умолчанию, если arg является None
, не разрешает эту проблему i> (я считаю, что это анти-шаблон по этой причине). Если вы исправите другую ошибку, избегая изменения значений аргументов независимо от того, имеют ли они значения по умолчанию, вы никогда не заметите или не позаботитесь об этом «удивительном». поведение.
– Ben
17 October 2017 в 21:44
Я иногда использую это поведение как альтернативу следующему шаблону:
singleton = None
def use_singleton():
global singleton
if singleton is None:
singleton = _make_singleton()
return singleton.use_me()
Если singleton
используется только use_singleton
, мне нравится следующий шаблон в качестве замены:
# _make_singleton() is called only once when the def is executed
def use_singleton(singleton=_make_singleton()):
return singleton.use_me()
Я использовал это для создания экземпляров клиентских классов, которые обращаются к внешним ресурсам, а также для создания dicts или списков для memoization.
Поскольку я не думаю, что этот шаблон хорошо известен, Я поставил короткий комментарий для защиты от будущих недоразумений.
_make_singleton
во время разбора в примере аргумента по умолчанию, но во время вызова в глобальном примере. Истинная подстановка будет использовать какой-то изменяемый поле для значения аргумента по умолчанию, но добавление аргумента дает возможность передавать альтернативные значения.
– Yann Vernier
19 November 2017 в 09:29
Раньше я думал, что создание объектов во время выполнения будет лучшим подходом. Теперь я менее уверен, так как вы теряете некоторые полезные функции, хотя это может стоить того, что было бы просто для предотвращения путаницы новичков. Недостатками этого являются:
1. Производительность
def foo(arg=something_expensive_to_compute())):
...
Если используется оценка времени вызова, тогда дорогая функция вызывается каждый раз, когда ваша функция используется без аргумента. Вы либо заплатили бы дорогостоящую цену за каждый вызов, либо должны вручную кэшировать значение извне, загрязняя пространство имен и добавляя многословие.
2. Принудительные связанные параметры
Полезный трюк заключается в привязке параметров лямбда к привязке current переменной при создании лямбда. Например:
funcs = [ lambda i=i: i for i in range(10)]
Возвращает список функций, возвращающих 0,1,2,3 ... соответственно. Если поведение изменено, они вместо этого привяжут i
к значению времени вызова для i, поэтому вы получите список функций, которые все вернули 9
.
Единственным способом реализации этого в противном случае было бы создать дальнейшее закрытие с привязкой i, то есть:
def make_func(i): return lambda: i
funcs = [make_func(i) for i in range(10)]
3. Introspection
Рассмотрим код:
def foo(a='test', b=100, c=[]):
print a,b,c
Мы можем получить информацию о аргументах и значениях по умолчанию с помощью модуля inspect
, который
>>> inspect.getargspec(foo)
(['a', 'b', 'c'], None, None, ('test', 100, []))
информация очень полезна для таких вещей, как создание документов, метапрограммирование, декораторы и т. д.
Теперь предположим, что поведение по умолчанию может быть изменено так, что это эквивалентно:
_undefined = object() # sentinel value
def foo(a=_undefined, b=_undefined, c=_undefined)
if a is _undefined: a='test'
if b is _undefined: b=100
if c is _undefined: c=[]
Однако мы потеряли способность интроспекции и посмотрим, какие аргументы по умолчанию равны . Поскольку объекты не были построены, мы никогда не сможем их захватить, не называя функцию. Самое лучшее, что мы могли бы сделать, это сохранить исходный код и вернуть его как строку.
1) Так называемая проблема «Mutable Default Argument» - это, в общем, специальный пример, демонстрирующий, что: «Все функции с этой проблемой также страдают от аналогичной проблемы побочного эффекта от фактического параметра». Это противоречит правилам
Пример:
def foo(a=[]): # the same problematic function
a.append(5)
return a
>>> somevar = [1, 2] # an example without a default parameter
>>> foo(somevar)
[1, 2, 5]
>>> somevar
[1, 2, 5] # usually expected [1, 2]
Решение: копия Совершенно безопасным решением является copy
или deepcopy
входной сигнал сначала, а затем делать все с копией.
def foo(a=[]):
a = a[:] # a copy
a.append(5)
return a # or everything safe by one line: "return a + [5]"
Многие встроенные изменяемые типы имеют метод копирования, такой как some_dict.copy()
или some_set.copy()
, или могут быть скопированы легко, как somelist[:]
или list(some_list)
, Каждый объект также может быть скопирован с помощью copy.copy(any_object)
или более тщательным с помощью copy.deepcopy()
(последний полезен, если изменяемый объект состоит из изменяемых объектов). Некоторые объекты основаны на побочных эффектах, таких как «файл», и не могут быть осмысленно воспроизведены копией. копирование
Пример проблемы для аналогичного вопроса SO
class Test(object): # the original problematic class
def __init__(self, var1=[]):
self._var1 = var1
somevar = [1, 2] # an example without a default parameter
t1 = Test(somevar)
t2 = Test(somevar)
t1._var1.append([1])
print somevar # [1, 2, [1]] but usually expected [1, 2]
print t2._var1 # [1, 2, [1]] but usually expected [1, 2]
Он не должен быть ни сохранен ни в одном ] public экземпляра, возвращаемого этой функцией. (Предполагая, что атрибуты private экземпляра не должны быть изменены извне этого класса или подкласса по соглашению, т. Е. _var1
является частным атрибутом)
Заключение: Параметры входных параметров shouldn 'изменяются на месте (мутированные), и они не должны быть привязаны к объекту, возвращаемому функцией. (Если мы предпочитаем программирование без побочных эффектов, которые настоятельно рекомендуются, см. Wiki о «побочном эффекте» (первые два абзаца имеют смысл в этом контексте.).)
2) Только если побочный эффект на фактический параметр требуется, но нежелателен по параметру по умолчанию, тогда полезным решением является def ...(var1=None):
if var1 is None:
var1 = []
Подробнее ..
3) В некоторых случаях используется изменчивое поведение параметров по умолчанию .
def f( a = None )
рекомендуется, когда вы действительно имеете в виду что-то еще. Копирование в порядке, потому что вы не должны мутировать аргументы. И когда вы делаете if a is None: a = [1, 2, 3]
, вы все равно копируете список.
– koddo
16 February 2018 в 17:15
Предположим, что у вас есть следующий код
fruits = ("apples", "bananas", "loganberries")
def eat(food=fruits):
...
Когда я вижу декларацию о еде, наименее удивительной является мысль, что если первый параметр не указан, он будет равен tuple ("apples", "bananas", "loganberries")
Однако, предположим позже в коде, я делаю что-то вроде
def some_random_function():
global fruits
fruits = ("blueberries", "mangos")
, тогда, если параметры по умолчанию были связаны с выполнением функции, а не с объявлением функции, тогда я бы удивляйтесь (очень плохо), чтобы обнаружить, что фрукты были изменены. Это было бы более удивительно ИМО, чем обнаружение того, что ваша функция foo
, указанная выше, мутировала список.
Реальная проблема заключается в изменяемых переменных, и все языки имеют определенную проблему. Возникает вопрос: предположим, что в Java у меня есть следующий код:
StringBuffer s = new StringBuffer("Hello World!");
Map<StringBuffer,Integer> counts = new HashMap<StringBuffer,Integer>();
counts.put(s, 5);
s.append("!!!!");
System.out.println( counts.get(s) ); // does this work?
Теперь, использует ли моя карта значение ключа StringBuffer
, когда оно было помещено на карту, или оно хранит ключ по ссылке? В любом случае, кто-то удивлен; либо человек, который попытался получить объект из Map
, используя значение, идентичное тому, с которым он положил его, или человек, который, похоже, не может получить свой объект, даже если ключ, который они используют, буквально тот же объект, который использовался для размещения его на карте (на самом деле Python не позволяет использовать его изменяемые встроенные типы данных в качестве словарных клавиш).
Ваш пример хороший случая, когда новички Python будут удивлены и укушены. Но я бы сказал, что если бы мы «исправили» это, тогда это создало бы другую ситуацию, в которой они были бы укушены, и это было бы еще менее интуитивным. Более того, это всегда имеет место при работе с изменяемыми переменными; вы всегда сталкиваетесь с ситуациями, когда кто-то может интуитивно ожидать одно или наоборот поведения в зависимости от того, какой код они пишут.
Мне лично нравится текущий подход Python: аргументы функции по умолчанию оцениваются, когда функция определена, и что объект всегда является значением по умолчанию. Я предполагаю, что они могут использовать специальный случай с пустым списком, но такой специальный корпус вызовет еще большее удивление, не говоря уже о несовместимости в обратном направлении.
("blueberries", "mangos")
.
– Ben Blank
15 July 2009 в 19:26
some_random_function()
присоединяется к fruits
вместо назначения ему, поведение eat()
будет i> изменяться. Так много для настоящего замечательного дизайна. Если вы используете аргумент по умолчанию, который упоминается в другом месте, а затем изменить ссылку из-за пределов функции, вы просите о проблемах. Настоящий WTF - это когда люди определяют новый аргумент по умолчанию (литерал списка или вызов конструктора), а still i> получают бит.
– alexis
9 October 2014 в 16:37
global
и переназначили кортеж - нет ничего удивительного, если eat
работает по-другому после этого.
– user3467349
26 January 2015 в 17:07
Такое поведение неудивительно, если принять во внимание следующее:
Роль (2) была широко освещена в этой теме. (1), вероятно, является фактором, вызывающим удивление, поскольку это поведение не является «интуитивным» при поступлении с других языков.
(1) описано в учебнике Python по классам . При попытке присвоить значение атрибуту класса только для чтения:
... все переменные, найденные вне самой внутренней области, доступны только для чтения ( попытка написать такую переменная просто создаст новую локальную переменную в самой внутренней области, оставив неизмененную идентичную внешнюю переменную неизменной ).
Оглянитесь на исходный пример и рассмотрите приведенные выше пункты:
def foo(a=[]): a.append(5) return a
Здесь
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
.Теперь сравните это с примером из документации по , эмулируя поведение аргументов по умолчанию других языков , так что значения по умолчанию для функции используются каждый раз, когда функция выполняется:
def foo(a, L=None): if L is None: L = [] L.append(a) return L
Принимая во внимание (1) и (2) , можно понять, почему это выполняет желаемое поведение:
- Когда объект функции
foo
создается,foo.func_defs[0]
установлен наNone
, неизменяемый объект.- Когда функция выполняется с настройками по умолчанию (без функции, заданной для
L
в вызове функции),foo.func_defs[0]
(None
) доступен в локальной области какL
.- После
L = []
присваивание не может преуспеть вfoo.func_defs[0]
, поскольку этот атрибут доступен только для чтения.- Per (1), новая локальная переменная с именем
L
создается в локальной области и используется для остальной части вызова функции.foo.func_defs[0]
, таким образом, остается неизменным для будущих вызововfoo
.
Назначение значений по умолчанию в вызове функции - это запах кода.
def a(b=[]):
pass
Это сигнатура функции, которая не подходит. Не только из-за проблем, описанных в других ответах. Я не буду вдаваться в это.
Эта функция направлена на то, чтобы сделать две вещи. Создайте новый список и выполните функциональность, скорее всего, в указанном списке.
Функции, которые делают две вещи, являются плохими функциями, поскольку мы учимся на чистых практиках кода.
Атака на эту проблему с полиморфизмом мы будем расширять список python или переносить его в класс, а затем выполнять свою функцию на нем.
Но подождите, пока вы скажете, мне нравятся мои однострочные.
Ну , Угадай, что. Код - это не просто способ управления поведением оборудования. Это способ:
Не оставляйте бомбы замедленного действия для себя
Отделяя эту функцию от двух вещей, которые она делает, нам нужен класс
class ListNeedsFives(object):
def __init__(self, b=None):
if b is None:
b = []
self.b = b
def foo():
self.b.append(5)
Выполнено
a = ListNeedsFives()
a.foo()
a.b
И почему это лучше, чем слияние всего вышеописанного кода с одной функцией.
def dontdothis(b=None):
if b is None:
b = []
b.append(5)
return b
Почему бы не сделать это?
Если вы не сработаете в своем проекте, ваш код будет жить. Скорее всего, ваша функция будет делать больше, чем это. Правильный способ создания поддерживаемого кода состоит в том, чтобы разделить код на атомные части с должным образом ограниченным объемом.
Конструктор класса является очень общепризнанным компонентом для всех, кто сделал объектно-ориентированное программирование. Размещение логики, которая обрабатывает экземпляр списка в конструкторе, делает когнитивную нагрузку понимания того, что делает код меньше.
Метод foo()
не возвращает список, почему бы и нет?
При возврате отдельного списка вы можете предположить, что безопасно делать то, что вам хочется. Но это может быть не так, поскольку он также разделяется объектом a
. Заставляя пользователя ссылаться на него как a.b
, он напоминает, где находится список. Любой новый код, который хочет изменить a.b
, естественно, будет помещен в класс, где он принадлежит.
Функция подписи def dontdothis(b=None):
не имеет ни одного из этих преимуществ.
Что вы спрашиваете, почему это:
def func(a=[], b = 2):
pass
не является внутренне эквивалентным этому:
def func(a=None, b = None):
a_default = lambda: []
b_default = lambda: 2
def actual_func(a=None, b=None):
if a is None: a = a_default()
if b is None: b = b_default()
return actual_func
func = func()
, за исключением случая прямого вызова func (None , None), которые мы будем игнорировать.
Другими словами, вместо оценки параметров по умолчанию, почему бы не сохранить их и оценить их при вызове функции?
Один ответ, вероятно, прямо там - он фактически превратит каждую функцию с параметрами по умолчанию в закрытие. Даже если это все скрыто в интерпретаторе, а не полномасштабное закрытие, данные должны быть где-то сохранены. Он будет медленнее и будет использовать больше памяти.
AFAICS никто еще не разместил соответствующую часть документации :
Значения параметров по умолчанию оцениваются при выполнении определения функции. Это означает, что выражение оценивается один раз, когда функция определена, и что для каждого вызова используется одно и то же «предварительно вычисленное» значение. Это особенно важно для понимания, когда параметр по умолчанию является изменяемым объектом, таким как список или словарь: если функция изменяет объект (например, добавив элемент в список), значение по умолчанию изменяется. Обычно это не то, что было предназначено. Способ вокруг этого - использовать None как значение по умолчанию и явно проверить его в теле функции [...]
function.data = []
) или еще лучше, создать объект.
– bukzor
19 June 2014 в 04:59
Простейшее обходное решение, использующее None
>>> def bar(b, data=None):
... data = data or []
... data.append(b)
... return data
...
>>> bar(3)
[3]
>>> bar(3)
[3]
>>> bar(3)
[3]
>>> bar(3, [34])
[34, 3]
>>> bar(3, [34])
[34, 3]
Это оптимизация производительности. В результате этой функциональности, какой из этих двух вызовов функций вы считаете более быстрым?
def print_tuple(some_tuple=(1,2,3)):
print some_tuple
print_tuple() #1
print_tuple((1,2,3)) #2
Я дам вам подсказку. Вот разборка (см. http://docs.python.org/library/dis.html ):
#
1 0 LOAD_GLOBAL 0 (print_tuple)
3 CALL_FUNCTION 0
6 POP_TOP
7 LOAD_CONST 0 (None)
10 RETURN_VALUE
#
2 0 LOAD_GLOBAL 0 (print_tuple)
3 LOAD_CONST 4 ((1, 2, 3))
6 CALL_FUNCTION 1
9 POP_TOP
10 LOAD_CONST 0 (None)
13 RETURN_VALUE
Я сомневаюсь, что опытное поведение имеет практическое применение (кто действительно использовал статические переменные в C без размножающихся ошибок?)
] Как вы можете видеть, - преимущество производительности при использовании неизменяемых аргументов по умолчанию. Это может иметь значение, если это часто называемая функция или аргумент по умолчанию занимает много времени, чтобы построить. Кроме того, имейте в виду, что Python не C. В C у вас есть константы, которые в значительной степени свободны. В Python у вас нет этого преимущества.
3 LOAD_CONST 4 ((1, 2, 3))
может превысить миллионы итераций? ^ 0 ^. Может быть, я буду профилировать и отчитываться ...
– Dmitry Minkovsky
31 March 2013 в 17:39
Вы можете обойти это, заменив объект (и, следовательно, связать область):
def foo(a=[]):
a = list(a)
a.append(5)
return a
Ужасно, но он работает.
Это легко объясняется:
Итак:
def x(a=0, b=[], c=[], d=0):
a = a + 1
b = b + [1]
c.append(1)
print a, b, c
a
не изменяется - каждый вызов назначения создает новый объект int - печатается новый объект b
не изменяется - новый массив создается из значения по умолчанию и печатается c
изменения - операция выполняется на одном и том же объекте - и печатается id(...)
для этой последней проверки или оператор is
отвечает на тот же вопрос?
– das-g
9 March 2016 в 09:09
is
будет отлично, я просто использовал id(val)
, потому что думаю, что это может быть более интуитивно понятным.
– Jim Fasarakis Hilliard
9 March 2016 в 09:20
input
. как input('Did you just see me without calling me?')
. Это делает его более понятным.
– Ev. Kounis
1 September 2017 в 09:22
Это может быть правдой, что:
полностью согласуется с обоими вышеперечисленными функциями и по-прежнему делает еще одну точку:
Другие ответы или, по крайней мере, некоторые из них либо делают точки 1 и 2, но не 3, либо делают точки 3 и нижние точки 1 и 2. Но все три являются истинными.
Возможно, что переключение лошадей в середине потока здесь потребует значительного поломки и что может возникнуть больше проблем, связанных с изменением Python, чтобы интуитивно обработать открывающий фрагмент Стефано. И это может быть правдой, что кто-то, кто хорошо знал внутренности Python, мог объяснить минные поля последствий. Однако
Существующее поведение не является Pythonic, а Python успешным, потому что очень мало о языке нарушает принцип наименьшего удивления где-нибудь возле , это плохо , Это настоящая проблема, было бы разумно ее искоренить. Это дефект дизайна. Если вы понимаете язык намного лучше, пытаясь проследить поведение, я могу сказать, что C ++ делает все это и многое другое; вы многому научитесь, перейдя, например, на тонкие ошибки указателя. Но это не Pythonic: людям, которые заботятся о Python достаточно, чтобы упорствовать перед лицом этого поведения, являются люди, которые тянутся к этому языку, потому что у Python гораздо меньше сюрпризов, чем на другом языке. Dabblers и любопытные становятся Pythonistas, когда они удивляются тому, как мало времени требуется, чтобы получить что-то работающее - не из-за дизайна fl - я имею в виду, скрытая логическая головоломка - которая урезает интуицию программистов, которые тянутся к Python потому что он просто работает.
x=[]
означает «создать пустой объект списка» и привязать к нему имя «x». & Quot; Таким образом, в def f(x=[])
также создается пустой список. Он не всегда связан с x, поэтому вместо этого он привязывается к суррогату по умолчанию. Позже, когда вызывается f (), значение по умолчанию выгружается и привязывается к x. Поскольку это был пустой список, который был безвозвратно, тот же список - единственное, что доступно для привязки к x, независимо от того, что в нем было застряло или нет. Как же может быть иначе?
– Jerry B
5 October 2013 в 07:18
Ну, причина в том, что привязки выполняются при выполнении кода, и выполняется определение функции, ну ... когда функции определены.
Сравните это:
class BananaBunch:
bananas = []
def addBanana(self, banana):
self.bananas.append(banana)
Этот код страдает от такого же неожиданного случая. bananas - это атрибут класса, и, следовательно, когда вы добавляете к нему вещи, он добавляется ко всем экземплярам этого класса. Причина в том, что это точно то же самое.
Это просто «Как это работает», и заставить его работать по-другому в функциональном случае, вероятно, будет сложно, а в случае класса вероятно невозможно или, по крайней мере, замедлить объект создание экземпляра, так как вам придется хранить код класса и выполнять его при создании объектов.
Да, это неожиданно. Но как только пенни падает, она прекрасно вписывается в то, как работает Python в целом. На самом деле, это хорошее учебное пособие, и как только вы поймете, почему это происходит, вы будете намного лучше читать python.
Это говорит о том, что он должен занимать видное место в любом хорошем учебнике Python. Потому что, как вы говорите, все рано или поздно сталкиваются с этой проблемой.
Решения здесь:
None
в качестве значения по умолчанию (или nonce object
) и включите его, чтобы создать свои значения во время выполнения; или lambda
в качестве параметра по умолчанию и вызовите его в блоке try, чтобы получить значение по умолчанию (это то, что требуется для лямбда-абстракции). Второй вариант хорош, потому что пользователи функции могут проходить в вызываемом, который может уже существовать (например, type
)
Это не ошибка дизайна . Любой, кто совершает это, делает что-то неправильно.
Есть три случая, когда я вижу, где вы можете столкнуться с этой проблемой:
cache={}
, и вы не должны были бы вызывать функцию с фактическим аргументом вообще. Пример в вопросе может относиться к категории 1 или 3. Нечетно, что он изменяет переданный список и возвращает его; вы должны выбрать тот или другой.
cache={}
- это действительно единственное решение для интервью, в реальном коде вы, вероятно, захотите @lru_cache
!
– Andy Hayden
17 October 2017 в 18:19
>>> def a():
>>> print "a executed"
>>> return []
>>> x =a()
a executed
>>> def b(m=[]):
>>> m.append(5)
>>> print m
>>> b(x)
[5]
>>> b(x)
[5, 5]
Эта «ошибка» дала мне много часов работы сверхурочных! Но я начинаю видеть его потенциальное использование (но мне хотелось бы, чтобы это было во время выполнения, все еще)
Я дам вам то, что я вижу в качестве полезного примера.
def example(errors=[]):
# statements
# Something went wrong
mistake = True
if mistake:
tryToFixIt(errors)
# Didn't work.. let's try again
tryToFixItAnotherway(errors)
# This time it worked
return errors
def tryToFixIt(err):
err.append('Attempt to fix it')
def tryToFixItAnotherway(err):
err.append('Attempt to fix it by another way')
def main():
for item in range(2):
errors = example()
print '\n'.join(errors)
main()
печатает следующие
Attempt to fix it
Attempt to fix it by another way
Attempt to fix it
Attempt to fix it by another way
Когда мы это делаем:
def foo(a=[]):
...
... мы присваиваем аргумент a
списку unnamed , если вызывающий объект не передает значение a .
Чтобы упростить эту дискуссию, давайте временно присвоим неназванному списку имя. Как насчет pavlo
?
def foo(a=pavlo):
...
В любое время, если вызывающий абонент не сообщает нам, что a
, мы повторно используем pavlo
.
Если pavlo
изменен (модифицируется), а foo
заканчивает его модификацию, эффект, который мы замечаем в следующий раз, foo
вызывается без указания a
.
Итак, это то, что вы видите (помните, pavlo
инициализируется в []):
>>> foo()
[5]
Теперь pavlo
- [5].
Вызов foo()
снова снова изменяет pavlo
:
>>> foo()
[5, 5]
Задание a
при вызове foo()
гарантирует, что pavlo
не коснулся.
>>> ivan = [1, 2, 3, 4]
>>> foo(a=ivan)
[1, 2, 3, 4, 5]
>>> ivan
[1, 2, 3, 4, 5]
Итак, pavlo
все еще [5, 5]
.
>>> foo()
[5, 5, 5]
Уже занятая тема, но из того, что я прочитал здесь, следующее помогло мне понять, как она работает внутри:
def bar(a=[]):
print id(a)
a = a + [1]
print id(a)
return a
>>> bar()
4484370232
4484524224
[1]
>>> bar()
4484370232
4484524152
[1]
>>> bar()
4484370232 # Never change, this is 'class property' of the function
4484523720 # Always a new object
[1]
>>> id(bar.func_defaults[0])
4484370232
a = a + [1]
перегрузки a
... рассмотреть возможность изменения его на b = a + [1] ; print id(b)
и добавить строку a.append(2)
. Это сделает более очевидным, что +
в двух списках всегда создает новый список (назначается b
), а модифицированный a
может по-прежнему иметь тот же id(a)
.
– Jörn Hees
8 April 2017 в 13:47
Я думаю, что ответ на этот вопрос заключается в том, как python передает данные в параметр (передать по значению или по ссылке), а не изменчивость или как питон обрабатывает инструкцию «def».
Краткое введение. Во-первых, в питоне есть два типа типов данных: один простой элементарный тип данных, например числа, а другой тип данных - объекты. Во-вторых, при передаче данных в параметры python передает элементарный тип данных по значению, т. Е. Делает локальную копию значения локальной переменной, но передает объект по ссылке, т. Е. Указывает на объект.
Признавая вышеуказанные два момента, давайте объясним, что произошло с кодом python. Это происходит только из-за передачи по ссылке для объектов, но не имеет ничего общего с mutable / immutable или, возможно, факта, что оператор «def» выполняется только один раз, когда он определен.
[] является объектом , поэтому python передает ссылку [] на a
, т. е. a
является только указателем на [], который лежит в памяти как объект. Существует только одна копия [] с, однако, многими ссылками на нее. Для первого foo () список [] изменен на 1 методом добавления. Но учтите, что существует только одна копия объекта списка, и этот объект теперь становится 1 . При запуске второго foo (), какая веб-страница effbot говорит (элементы больше не оцениваются) неверна. a
оценивается как объект списка, хотя теперь содержимое объекта 1 . Это эффект прохождения по ссылке! Результат foo (3) может быть легко получен таким же образом.
Чтобы еще раз подтвердить мой ответ, давайте рассмотрим два дополнительных кода.
===== = № 2 ========
def foo(x, items=None):
if items is None:
items = []
items.append(x)
return items
foo(1) #return [1]
foo(2) #return [2]
foo(3) #return [3]
[]
- это объект, поэтому None
(первый является изменяемым, в то время как последний является неизменным, но изменчивость не имеет ничего что касается вопроса). Никто не находится где-то в пространстве, но мы знаем, что он есть, и там есть только одна копия «Нет». Таким образом, каждый раз, когда вызывается foo, элементы оцениваются (в отличие от некоторого ответа, который оценивается только один раз) равны None, чтобы быть ясным, ссылка (или адрес) None. Затем в foo элемент изменяется на [], т. Е. Указывает на другой объект, который имеет другой адрес.
====== № 3 =======
def foo(x, items=[]):
items.append(x)
return items
foo(1) # returns [1]
foo(2,[]) # returns [2]
foo(3) # returns [1,3]
Вызов элемента foo (1) указывает на объект списка [] с адрес, скажем, 11111111. содержимое списка будет изменено на 1 в функции foo в дальнейшем, но адрес не изменится, еще 11111111. Тогда foo (2, []) является приходит. Хотя [] в foo (2, []) имеет тот же контент, что и параметр по умолчанию [] при вызове foo (1), их адрес отличается! Поскольку мы предоставляем параметр явно, items
должен принять адрес этого нового []
, скажем 2222222, и вернуть его после внесения некоторых изменений. Выполняется foo (3). поскольку предоставляется только x
, элементы должны снова принимать значение по умолчанию. Что такое значение по умолчанию? Он задается при определении функции foo: объект списка, расположенный в 11111111. Таким образом, элементы оцениваются как адрес 11111111, имеющий элемент 1. Список, расположенный в 2222222, также содержит один элемент 2, но он не указывается элементами any Больше. Следовательно, добавление 3 сделает items
[1,3].
Из приведенных выше объяснений видно, что веб-страница effbot , рекомендованная в принятом ответе, не дала соответствующего ответа на этот вопрос. Более того, я думаю, что точка на веб-странице effbot неверна. Я думаю, что код UI.Button верен:
for i in range(10):
def callback():
print "clicked button", i
UI.Button("button %s" % i, callback)
Каждая кнопка может содержать определенную функцию обратного вызова, которая будет отображать различное значение i
. Я могу привести пример, чтобы показать это:
x=[]
for i in range(10):
def callback():
print(i)
x.append(callback)
Если мы выполним x[7]()
, мы получим 7, как ожидалось, и x[9]()
даст 9, другое значение i
.
x[7]()
- 9
.
– Duncan
2 October 2013 в 14:29
Просто измените функцию:
def notastonishinganymore(a = []): '''The name is just a joke :)'''
a = a[:]
a.append(5)
return a
functions are objects
. В вашей парадигме предложение будет состоять в том, чтобы реализовать значения функций по умолчанию как свойства, а не атрибуты. – bukzor 3 May 2014 в 21:46