Мне нужна функция обратного вызова, которая почти одинакова для серии событий графического интерфейса. Функция будет вести себя немного по-разному в зависимости от того, какое событие ее вызвало. Мне кажется простой случай, но я не могу понять этого странного поведения лямбда-функций.
Итак, у меня есть следующий упрощенный код ниже:
def callback(msg):
print msg
#creating a list of function handles with an iterator
funcList=[]
for m in ('do', 're', 'mi'):
funcList.append(lambda: callback(m))
for f in funcList:
f()
#create one at a time
funcList=[]
funcList.append(lambda: callback('do'))
funcList.append(lambda: callback('re'))
funcList.append(lambda: callback('mi'))
for f in funcList:
f()
Вывод этого кода:
mi
mi
mi
do
re
mi
Я ожидал:
do
re
mi
do
re
mi
Почему использование итератора все испортило?
Я пробовал использовать глубокую копию:
import copy
funcList=[]
for m in ('do', 're', 'mi'):
funcList.append(lambda: callback(copy.deepcopy(m)))
for f in funcList:
f()
Но здесь та же проблема.
python
lexical-closures
Agartland
источник
источник
Ответы:
Проблема здесь в том, что
m
переменная (ссылка) берется из окружающей области. В области лямбда содержатся только параметры.Чтобы решить эту проблему, вам нужно создать еще одну область для лямбда:
def callback(msg): print msg def callback_factory(m): return lambda: callback(m) funcList=[] for m in ('do', 're', 'mi'): funcList.append(callback_factory(m)) for f in funcList: f()
В приведенном выше примере лямбда также использует для поиска окружающую область видимости
m
, но на этот раз онаcallback_factory
создается один раз при каждомcallback_factory
вызове.Или с functools.partial :
from functools import partial def callback(msg): print msg funcList=[partial(callback, m) for m in ('do', 're', 'mi')] for f in funcList: f()
источник
Когда лямбда создается, она не копирует переменные в охватывающей области, которую она использует. Он поддерживает ссылку на среду, чтобы позже можно было найти значение переменной. Есть только один
m
. Он назначается каждый раз в цикле. После цикла переменнаяm
имеет значение'mi'
. Поэтому, когда вы действительно запускаете функцию, которую создали позже, она будет искать значениеm
в среде, которая ее создала, которая к тому времени будет иметь значение'mi'
.Одним из распространенных и идиоматических решений этой проблемы является захват значения
m
в момент создания лямбда, используя его в качестве аргумента по умолчанию необязательного параметра. Обычно вы используете параметр с тем же именем, поэтому вам не нужно менять тело кода:for m in ('do', 're', 'mi'): funcList.append(lambda m=m: callback(m))
источник
Конечно, Python использует ссылки, но в данном контексте это не имеет значения.
Когда вы определяете лямбда (или функцию, поскольку это точно такое же поведение), она не оценивает лямбда-выражение до выполнения:
# defining that function is perfectly fine def broken(): print undefined_var broken() # but calling it will raise a NameError
Еще более удивительно, чем ваш пример лямбда:
i = 'bar' def foo(): print i foo() # bar i = 'banana' foo() # you would expect 'bar' here? well it prints 'banana'
Короче говоря, думайте динамично: перед интерпретацией ничего не оценивается, поэтому в вашем коде используется последнее значение m.
Когда он ищет m в выполнении лямбда, m берется из самой верхней области видимости, что означает, что, как указывали другие; вы можете обойти эту проблему, добавив еще одну область:
def factory(x): return lambda: callback(x) for m in ('do', 're', 'mi'): funcList.append(factory(m))
Здесь, когда лямбда вызывается, она ищет x в области определения лямбда. Этот x - локальная переменная, определенная в теле фабрики. По этой причине значение, используемое при выполнении лямбда-выражения, будет значением, переданным в качестве параметра во время вызова factory. И дореми!
В качестве примечания, я мог бы определить factory как factory (m) [заменить x на m], поведение такое же. Для ясности я использовал другое название :)
Вы можете обнаружить, что у Андрея Бауэра похожие проблемы с лямбда. Что интересно в этом блоге, так это комментарии, где вы узнаете больше о закрытии python :)
источник
Не имеет прямого отношения к рассматриваемой проблеме, но, тем не менее, представляет собой бесценную мудрость: объекты Python от Фредрика Лунда.
источник
Да, это проблема области видимости, она привязана к внешнему m, независимо от того, используете ли вы лямбду или локальную функцию. Вместо этого используйте функтор:
class Func1(object): def __init__(self, callback, message): self.callback = callback self.message = message def __call__(self): return self.callback(self.message) funcList.append(Func1(callback, m))
источник
решение лямбда больше лямбда
In [0]: funcs = [(lambda j: (lambda: j))(i) for i in ('do', 're', 'mi')] In [1]: funcs Out[1]: [<function __main__.<lambda>>, <function __main__.<lambda>>, <function __main__.<lambda>>] In [2]: [f() for f in funcs] Out[2]: ['do', 're', 'mi']
наружный
lambda
используются для связывания текущего значения ,i
чтобыj
накаждый раз, когда
lambda
вызывается внешний, он создает экземпляр внутреннегоlambda
сj
привязкой к текущему значениюi
asi
valueисточник
Во-первых, то, что вы видите, не является проблемой и не связано с вызовом по ссылке или по значению.
Определенный вами лямбда-синтаксис не имеет параметров, и поэтому область видимости, которую вы видите с параметром,
m
является внешней по отношению к лямбда-функции. Вот почему вы видите такие результаты.Лямбда-синтаксис в вашем примере не нужен, и вы бы предпочли использовать простой вызов функции:
for m in ('do', 're', 'mi'): callback(m)
Опять же, вы должны быть очень точны в том, какие лямбда-параметры вы используете и где именно начинается и заканчивается их область действия.
В качестве примечания относительно передачи параметров. Параметры в Python всегда являются ссылками на объекты. Процитирую Алекса Мартелли:
источник
Переменная
m
фиксируется, поэтому ваше лямбда-выражение всегда видит свое «текущее» значение.Если вам нужно эффективно захватить значение в определенный момент времени, напишите функцию, которая принимает желаемое значение в качестве параметра и возвращает лямбда-выражение. В этот момент лямбда будет фиксировать значение параметра , которое не изменится, если вы вызовете функцию несколько раз:
def callback(msg): print msg def createCallback(msg): return lambda: callback(msg) #creating a list of function handles with an iterator funcList=[] for m in ('do', 're', 'mi'): funcList.append(createCallback(m)) for f in funcList: f()
Выход:
источник
на самом деле в Python нет переменных в классическом понимании, только имена, связанные ссылками на соответствующий объект. Даже функции в Python являются своего рода объектами, и лямбды не являются исключением из правил :)
источник
В качестве побочного примечания,
map
хотя некоторые известные фигуры Python и презирают его, он заставляет конструкцию, предотвращающую эту ловушку.fs = map (lambda i: lambda: callback (i), ['do', 're', 'mi'])
NB: первый
lambda i
действует как завод в других ответах.источник