¿Cómo creo una lista de Python lambdas (en una lista de comprensión / para bucle)?

Quiero crear una lista de objetos lambda a partir de una lista de constantes en Python; por ejemplo:

listOfNumbers = [1,2,3,4,5] square = lambda x: x * x listOfLambdas = [lambda: square(i) for i in listOfNumbers] 

Esto creará una lista de objetos lambda, sin embargo, cuando los ejecute:

 for f in listOfLambdas: print f(), 

Espero que se imprima

 1 4 9 16 25 

En su lugar, imprime:

 25 25 25 25 25 

Parece que a todos los lambdas se les ha dado el parámetro incorrecto. ¿He hecho algo mal y hay una manera de solucionarlo? Estoy en Python 2.4, creo.

EDITAR: un poco más de intentar cosas y así surgió esto:

 listOfLambdas = [] for num in listOfNumbers: action = lambda: square(num) listOfLambdas.append(action) print action() 

Imprime los cuadrados esperados del 1 al 25, pero luego usa la statement de impresión anterior:

 for f in listOfLambdas: print f(), 

Todavía me da todos los 25 s. ¿Cómo cambiaron los objetos lambda existentes entre esas dos llamadas de impresión?

Pregunta relacionada: ¿Por qué los resultados de map () y la comprensión de listas son diferentes?

Supongo que la lambda que está creando en la lista de comprensión está vinculada a la variable i que finalmente termina en 5. Por lo tanto, cuando evalúa las lambdas después del hecho, todas están vinculadas a 5 y terminan calculando 25. Lo mismo está sucediendo con num en su segundo ejemplo. Cuando evalúa la lambda dentro del bucle, el número no ha cambiado, por lo que obtiene el valor correcto. Después del bucle, num es 5 …

No estoy muy seguro de a qué se dirige, así que no estoy seguro de cómo sugerir una solución. ¿Qué tal esto?

 def square(x): return lambda : x*x listOfLambdas = [square(i) for i in [1,2,3,4,5]] for f in listOfLambdas: print f() 

Esto me da la salida esperada:

 1 4 9 16 25 

Otra forma de pensar esto es que un lambda “captura” su entorno léxico en el punto donde se crea. Por lo tanto, si le asigna un número, en realidad no resuelve ese valor hasta que se invoca. Esto es a la vez confuso y poderoso.

Tienes:

 listOfLambdas = [lambda: i*i for i in range(6)] for f in listOfLambdas: print f() 

Salida:

 25 25 25 25 25 25 

Necesitas curry! Aparte de ser delicioso, usa este valor predeterminado “hackear”.

 listOfLambdas = [lambda i=i: i*i for i in range(6)] for f in listOfLambdas: print f() 

Salida:

 0 1 4 9 16 25 

Tenga en cuenta la i=i . Ahí es donde ocurre la magia.

Cuando se ejecutan los enunciados de funciones, se enlazan a su ámbito adjunto (léxicamente).

En su fragmento, los lambdas están vinculados al ámbito global, porque for suites no se ejecutan como una unidad de ámbito independiente en Python. Al final del bucle for , el num está enlazado en el ámbito de envolvente. Manifestación:

 for num in range(1, 6): pass assert num == 5 # num is now bound in the enclosing scope 

Por lo tanto, cuando enlaza los identificadores en el bucle for , en realidad está manipulando el ámbito adjunto.

 for num in range(1, 6): spam = 12 assert num == 5 # num is now bound in the enclosing scope assert spam == 12 # spam is also bound in the enclosing scope 

El mismo trato para las listas de comprensión:

 [num for num in range(1, 6)] assert num == 5 

Alucinante, lo sé. En cualquier caso, con nuestro nuevo conocimiento, podemos determinar que las lambdas que está creando se refieren al (único) identificador num delimitado en el ámbito que lo contiene. Eso debería hacer que esto tenga más sentido:

 functions = [] for number in range(1, 6): def fun(): return number functions.append(fun) assert all(fun() == 5 for fun in functions) assert all(fun() is number for fun in functions) 

Y aquí está la parte más genial que lo demuestra aún más:

 # Same as above -- commented out for emphasis. #functions = [] #for number in range(1, 6): # def fun(): # return number # functions.append(fun) #assert all(fun() == 5 for fun in functions) #assert all(fun() is number for fun in functions) number = 6 # Rebind 6 in the scope and see how it affects the results. assert all(fun() == 6 for fun in functions) 

Por lo tanto, la solución a esto, por supuesto, es hacer un nuevo ámbito de cierre para cada number que desee vincular. En Python, puedes crear nuevos ámbitos de encierro con módulos, clases y funciones. Es común usar una función solo para crear un nuevo ámbito de cierre para otra función.

En Python, un cierre es una función que devuelve otra función . Algo así como un constructor de funciones. Echa un vistazo a get_fun en el siguiente ejemplo:

 def get_fun(value): """:return: A function that returns :param:`value`.""" def fun(): # Bound to get_fun's scope return value return fun functions = [] for number in range(1, 6): functions.append(get_fun(number)) assert [fun() for fun in functions] == range(1, 6) 

Como get_fun es una función, tiene su propio ámbito interno. Cada vez que llama a get_fun con un valor, se crea una pequeña tabla para realizar un seguimiento de los enlaces dentro de ella; es decir, dice: “Dentro de este scope, el identificador de value apunta a lo que se pasó”. Ese scope desaparece al final de la ejecución de la función, a menos que haya una razón para que se quede.

Si está devolviendo una función desde dentro de un scope, esa es una buena razón para que partes de la “tabla de scope” se queden: esa función que está devolviendo podría hacer referencia a cosas de esa tabla de scope cuando la llame más adelante. Por esa razón, cuando se crea fun dentro de get_fun Python le dice a la fun sobre la tabla de scope de get_fun , que es fun útil cuando es necesario.

Puede leer más sobre los detalles y la terminología técnica (que suavicé un poco) en los documentos de Python en el modelo de ejecución . También puede ver las partes del ámbito de print fun.__closure__ a las que se refiere una función con print fun.__closure__ . En lo anterior, vemos la referencia al value , que pasa a ser un int:

 # Same as before, commented out for emphasis. #functions = [] #for number in range(1, 6): # functions.append(get_fun(number)) #assert [fun() for fun in functions] == range(1, 6) print functions[0].__closure__ # Produces: (,) 
 listOfLambdas = [lambda i=i: square(i) for i in listOfNumbers] 

O

 listOfLambdas = map(lambda i: lambda: square(i), listOfNumbers) 

A veces encuentro que definir clases reales para objetos de función hace que sea más fácil entender lo que está pasando:

 >>> class square(object): ... def __init__(self, val): ... self.val = val ... def __call__(self): ... return self.val * self.val ... >>> l = [1,2,3,4,5] >>> funcs = [square(i) for i in l] >>> for f in funcs: ... print f() ... 1 4 9 16 25 >>> 

Por supuesto, es un poco más detallado que usar lambdas o cierres, pero me parece más fácil de entender cuando trato de hacer cosas no obvias con funciones.

Trate de usar () en lugar de []:

 listOfLambdas = (lambda: square(i) for i in listOfNumbers) 

Y obtendrás:

 1 4 9 16 25 

También podrías hacer:

 >>> def squares(): ... for i in [1,2,3,4,5]: ... yield lambda:i*i ... >>> print [square() for square in squares()] [1, 4, 9, 16, 25] 

Como comentario adicional, me gustaría delinear la posibilidad de generar listas de funciones lambda a partir de matrices sympy (no sé si es la mejor manera de hacerlo, pero así es como lo hago y lo encuentro conveniente):

 import sympy as sp sp.var('Ksi') # generate sympy expressions for Berstein's polynomials B_expr = sp.Matrix([sp.binomial(3, i) * Ksi**i * (1 - Ksi)**(3-i) for i in range(4)]) # lambdify them B = [sp.lambdify((Ksi), B_expr[i]) for i in range(4) ]