Объем лямбда-функций и их параметры?

Мне нужна функция обратного вызова, которая почти такая же для серии событий gui. Функция будет вести себя несколько иначе, в зависимости от того, какое событие вызвало это событие. Кажется, это простой случай, но я не могу понять это странное поведение лямбда-функций.

Итак, у меня есть следующий упрощенный код:

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

Почему использование итератора перепутано?

Я пробовал использовать deepcopy:

import copy
funcList=[]
for m in ('do', 're', 'mi'):
    funcList.append(lambda: callback(copy.deepcopy(m)))
for f in funcList:
    f()

Но это имеет ту же проблему.

Ответ 1

Проблема здесь - это переменная 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()

Ответ 2

Когда создается лямбда, она не создает копию переменных в охватывающей области, которую она использует. Он поддерживает ссылку на среду, чтобы впоследствии просмотреть значение переменной. Существует только один m. Он привязывается к каждому циклу. После цикла переменная m имеет значение 'mi'. Поэтому, когда вы фактически запускаете функцию, которую вы создали позже, она будет искать значение m в среде, которая его создала, и к тому моменту будет иметь значение 'mi'.

Одно общее и идиоматическое решение этой проблемы состоит в том, чтобы зафиксировать значение m во время создания лямбда, используя его в качестве аргумента по умолчанию для необязательного параметра. Обычно вы используете параметр с тем же именем, поэтому вам не нужно менять тело кода:

for m in ('do', 're', 'mi'):
    funcList.append(lambda m=m: callback(m))

Ответ 3

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 как factory (m) [заменить x на m], поведение будет одинаковым. Я использовал другое название для ясности:)

Вы можете обнаружить, что Andrej Bauer получили аналогичные проблемы с лямбдой. Что интересного в этом блоге - это комментарии, в которых вы узнаете больше о закрытии python:)

Ответ 4

Не имеет прямого отношения к проблеме, но бесценный кусок мудрости: Объекты Python от Fredrik Lundh.

Ответ 5

Во-первых, то, что вы видите, не является проблемой и не связано с вызовом по ссылке или по значению.

Определенный вами синтаксис лямбда не имеет параметров, и, таким образом, область видимости с параметром m является внешней по отношению к лямбда-функции. Вот почему вы видите эти результаты.

Синтаксис Lambda в вашем примере не нужен, и вы предпочтете использовать простой вызов функции:

for m in ('do', 're', 'mi'):
    callback(m)

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

В качестве побочного примечания, касающегося передачи параметров. Параметры в python всегда ссылаются на объекты. Процитировать Alex Martelli:

Терминологическая проблема может быть вызвана тот факт, что в python значение имя является ссылкой на объект. Итак, вы всегда передаете значение (нет неявное копирование), и это значение всегда ссылка. [...] Теперь, если вы хотите присвоить имя для этого, например, "по объектной ссылке", "необоснованным" ценность "или что-то еще, будь моим гостем. Попытка повторного использования терминологии, которая более широко применяется к языкам где" переменные являются полями "для язык, где" переменные являются post-it теги ", ИМХО, с большей вероятностью запутать чем помочь.

Ответ 6

Переменная 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()

Вывод:

do
re
mi

Ответ 7

Да, проблема с областью видимости, она привязывается к внешнему 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))

Ответ 8

на самом деле нет никаких переменных в классическом смысле в Python, просто имена, привязанные ссылками на применимый объект. Даже функции - это своего рода объект в Python, и lambdas не делают исключение из правила:)

Ответ 9

В качестве примечания стороны map, хотя и презираемая какой-то известной фигурой Питона, заставляет конструкцию, которая предотвращает эту ловушку.

fs = map (lambda i: lambda: callback (i), ['do', 're', 'mi'])

NB: первый lambda i действует как factory в других ответах.

Ответ 10

soluiton to lambda больше лямбда

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 как i value