Вопрос-Ответ

Lambda in a loop [duplicate]

Лямбда в цикле

Рассматриваем следующий фрагмент кода:

# directorys == {'login': <object at ...>, 'home': <object at ...>}
for d in directorys:
self.command["cd " + d] = (lambda : self.root.change_directory(d))

Я ожидаю создать словарь из двух функций следующим образом :

# Expected :
self.command == {
"cd login": lambda: self.root.change_directory("login"),
"cd home": lambda: self.root.change_directory("home")
}

но похоже, что две сгенерированные лямбда-функции абсолютно одинаковы :

# Result :
self.command == {
"cd login": lambda: self.root.change_directory("login"),
"cd home": lambda: self.root.change_directory("login") # <- Why login ?
}

Я действительно не понимаю почему. У вас есть какие-либо предложения?

Переведено автоматически
Ответ 1

Вам нужно привязать d для каждой созданной функции. Один из способов сделать это - передать его как параметр со значением по умолчанию:

lambda d=d: self.root.change_directory(d)

Теперь d внутри функции использует параметр, даже если он имеет то же имя, и значение по умолчанию для него вычисляется при создании функции. Чтобы помочь вам увидеть это.:

lambda bound_d=d: self.root.change_directory(bound_d)

Помните, как работают значения по умолчанию, например, для изменяемых объектов, таких как списки и dicts , потому что вы привязываете объект.

Эта идиома параметров со значениями по умолчанию достаточно распространена, но может дать сбой, если вы проанализируете параметры функции и определите, что делать, основываясь на их наличии. Вы можете избежать использования параметра с помощью другого замыкания.:

(lambda d=d: lambda: self.root.change_directory(d))()
# or
(lambda d: lambda: self.root.change_directory(d))(d)
Ответ 2

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

Для более простого примера:

funcs = []
for x in [1,2,3]:
funcs.append(lambda: x)

for f in funcs:
print f()

# output:
3
3
3

Вы можете обойти это, добавив дополнительную функцию, например:

def makeFunc(x):
return lambda: x

funcs = []
for x in [1,2,3]:
funcs.append(makeFunc(x))

for f in funcs:
print f()

# output:
1
2
3

Вы также можете исправить область видимости внутри лямбда-выражения

lambda bound_x=x: bound_x

Однако в целом это не хорошая практика, поскольку вы изменили сигнатуру своей функции.

Ответ 3

В качестве альтернативы, вместо lambda вы можете использовать functools.partial который, на мой взгляд, имеет более чистый синтаксис.

Вместо:

for d in directorys:
self.command["cd " + d] = (lambda d=d: self.root.change_directory(d))

это будет:

for d in directorys:
self.command["cd " + d] = partial(self.root.change_directory, d)

Или, вот еще один простой пример:

numbers = [1, 2, 3]

lambdas = [lambda: print(number)
for number in numbers]
lambdas_with_binding = [lambda number=number: print(number)
for number in numbers]
partials = [partial(print, number)
for number in numbers]

for function in lambdas:
function()
# 3 3 3
for function in lambdas_with_binding:
function()
# 1 2 3
for function in partials:
function()
# 1 2 3
Ответ 4

Я столкнулся с такой же проблемой. Выбранное решение мне очень помогло, но я считаю необходимым добавить точности, чтобы сделать функциональным код вопроса: определите лямбда-функцию вне цикла. Кстати, значение по умолчанию не обязательно.

foo = lambda d: lambda : self.root.change_directory(d)
for d in directorys:
self.command["cd " + d] = (foo(d))
python