Python лямбда в петле
Учитывая следующий фрагмент кода:
# 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 ?
}
Я действительно не понимаю, почему. У вас есть какие-нибудь предложения?
3 ответа
Вам нужно связать d для каждой созданной функции. Один из способов сделать это - передать его как параметр со значением по умолчанию:
lambda d=d: self.root.change_directory(d)
Теперь d внутри функции использует параметр, даже если он имеет то же имя, и значение по умолчанию для него оценивается при создании функции. Чтобы помочь вам увидеть это:
lambda bound_d=d: self.root.change_directory(bound_d)
Вспомните, как работают значения по умолчанию, например, для изменяемых объектов, таких как списки и подсказки, поскольку вы привязываете объект.
Эта идиома параметров со значениями по умолчанию достаточно распространена, но может не сработать, если вы проанализируете параметры функции и определите, что делать, основываясь на их присутствии. Вы можете избежать параметра с другим замыканием:
(lambda d=d: lambda: self.root.change_directory(d))()
# or
(lambda d: lambda: self.root.change_directory(d))(d)
Это связано с тем, что точка 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
Однако в целом это не очень хорошая практика, поскольку вы изменили сигнатуру своей функции.
Alternatively, instead of lambda
, you can use functools.partial
which, in my opinion, has a cleaner syntax.
Instead of:
for d in directorys:
self.command["cd " + d] = (lambda d=d: self.root.change_directory(d))
it will be:
for d in directorys:
self.command["cd " + d] = partial(self.root.change_directory, d)
Or, here is another simple example:
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
Я встретил ту же проблему. Выбранное решение мне очень помогло, но я считаю необходимым добавить точность, чтобы сделать функционал кодом вопроса: определить лямбда-функцию вне цикла. Кстати, значение по умолчанию не обязательно.
foo = lambda d: lambda : self.root.change_directory(d)
for d in directorys:
self.command["cd " + d] = (foo(d))