Lambda en un bucle [duplicado]

Resuelto FunkySayu asked hace 11 años • 4 respuestas

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 ?

FunkySayu avatar Nov 07 '13 20:11 FunkySayu
Aceptado

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)
None avatar Nov 07 '2013 13:11 None

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 den 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.

robbie_c avatar Nov 07 '2013 13:11 robbie_c

Alternativamente, en lugar de lambda, puedes usar functools.partialel 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
Georgy avatar Jul 31 '2019 10:07 Georgy

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))
MonsterBat Doppelgänger avatar Oct 20 '2017 10:10 MonsterBat Doppelgänger