Подтвердить что ты не робот

Python Lambda в цикле

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

# 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")
}

но похоже, что две генерируемые lambda функции точно такие же:

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

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

4b9b3361

Ответ 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

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

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