Lambda en un bucle [duplicado]
Teniendo en cuenta el siguiente fragmento de código:
# directorys == {'login': <object at ...>, 'home': <object at ...>}
for d in directorys:
self.command["cd " + d] = (lambda : self.root.change_directory(d))
Espero crear un diccionario de dos funciones de la siguiente manera:
# Expected :
self.command == {
"cd login": lambda: self.root.change_directory("login"),
"cd home": lambda: self.root.change_directory("home")
}
pero parece que las dos funciones lambda generadas son exactamente iguales:
# Result :
self.command == {
"cd login": lambda: self.root.change_directory("login"),
"cd home": lambda: self.root.change_directory("login") # <- Why login ?
}
Realmente no entiendo por qué. Tienes alguna sugerencia ?
Debe vincular d para cada función creada. Una forma de hacerlo es pasarlo como parámetro con un valor predeterminado:
lambda d=d: self.root.change_directory(d)
Ahora la d dentro de la función usa el parámetro, aunque tiene el mismo nombre, y el valor predeterminado se evalúa cuando se crea la función. Para ayudarte a ver esto:
lambda bound_d=d: self.root.change_directory(bound_d)
Recuerde cómo funcionan los valores predeterminados, como para objetos mutables como listas y dictados, porque está vinculando un objeto.
Este modismo de parámetros con valores predeterminados es bastante común, pero puede fallar si realiza una introspección de los parámetros de la función y determina qué hacer en función de su presencia. Puedes evitar el parámetro con otro cierre:
(lambda d=d: lambda: self.root.change_directory(d))()
# or
(lambda d: lambda: self.root.change_directory(d))(d)
Esto se debe al punto en el que se une d. Todas las funciones lambda apuntan a la variable d
en lugar del valor actual de la misma, por lo que cuando actualiza d
en la siguiente iteración, esta actualización se ve en todas sus funciones.
Para un ejemplo más simple:
funcs = []
for x in [1,2,3]:
funcs.append(lambda: x)
for f in funcs:
print f()
# output:
3
3
3
Puedes solucionar esto agregando una función adicional, como esta:
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
También puedes arreglar el alcance dentro de la expresión lambda.
lambda bound_x=x: bound_x
Sin embargo, en general, esta no es una buena práctica ya que ha cambiado la firma de su función.
Alternativamente, en lugar de lambda
, puedes usar functools.partial
el cual, en mi opinión, tiene una sintaxis más limpia.
En lugar de:
for d in directorys:
self.command["cd " + d] = (lambda d=d: self.root.change_directory(d))
será:
for d in directorys:
self.command["cd " + d] = partial(self.root.change_directory, d)
O aquí hay otro ejemplo simple:
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
Me encontré con el mismo problema. La solución seleccionada me ayudó mucho, pero considero necesario agregar una precisión para hacer funcional el código de la pregunta: definir la función lambda fuera del bucle. Por cierto, el valor predeterminado no es necesario.
foo = lambda d: lambda : self.root.change_directory(d)
for d in directorys:
self.command["cd " + d] = (foo(d))