Python lambda cierre de scope

Estoy tratando de usar cierres para eliminar una variable de una firma de función (la aplicación es para escribir todas las funciones necesarias para conectar señales Qt para una interfaz para controlar una gran cantidad de parámetros en el diccionario que almacena los valores).

No entiendo por qué el caso de usar el lambda no envuelto en otra función devuelve el apellido para todos los casos.

 names = ['a', 'b', 'c'] def test_fun(name, x): print(name, x) def gen_clousure(name): return lambda x: test_fun(name, x) funcs1 = [gen_clousure(n) for n in names] funcs2 = [lambda x: test_fun(n, x) for n in names] # this is what I want In [88]: for f in funcs1: ....: f(1) a 1 b 1 c 1 # I do not understand why I get this In [89]: for f in funcs2: ....: f(1) c 1 c 1 c 1 

La razón es que los cierres (lambdas u otros) se cierran sobre los nombres, no sobre los valores. Cuando define lambda x: test_fun(n, x) , la n no se evalúa, porque está dentro de la función. Se evalúa cuando se llama a la función, momento en el cual el valor que está allí es el último valor del bucle.

Dice al principio que desea “utilizar cierres para eliminar una variable de una firma de función”, pero en realidad no funciona de esa manera. (Consulte a continuación, sin embargo, una forma que puede satisfacerlo, dependiendo de lo que quiere decir con “eliminar”). Las variables dentro del cuerpo de la función no se evaluarán cuando se defina la función. Para que la función tome una “instantánea” de la variable tal como existe en el momento de la definición de la función, debe pasar la variable como un argumento. La forma habitual de hacer esto es dar a la función un argumento cuyo valor predeterminado sea la variable del ámbito externo. Mira la diferencia entre estos dos ejemplos:

 >>> stuff = [lambda x: n+x for n in [1, 2, 3]] >>> for f in stuff: ... print f(1) 4 4 4 >>> stuff = [lambda x, n=n: n+x for n in [1, 2, 3]] >>> for f in stuff: ... print f(1) 2 3 4 

En el segundo ejemplo, pasar n como argumento a la función “bloquea” el valor actual de n a esa función. Tienes que hacer algo como esto si quieres bloquear el valor de esta manera. (Si no funcionara de esta manera, las variables globales no funcionarían en absoluto; es esencial que las variables libres se consulten en el momento de su uso).

Tenga en cuenta que nada de este comportamiento es específico de las lambdas. Las mismas reglas de scope están en vigencia si usa def para definir una función que haga referencia a variables del scope adjunto.

Si realmente lo desea, puede evitar agregar el argumento adicional a su función devuelta, pero para hacerlo debe ajustar esa función en otra función, como por ejemplo:

 >>> def makeFunc(n): ... return lambda x: x+n >>> stuff = [makeFunc(n) for n in [1, 2, 3]] >>> for f in stuff: ... print f(1) 2 3 4 

Aquí, el lambda interno aún busca el valor de n cuando se llama. Pero la n que hace referencia ya no es una variable global, sino una variable local dentro de la función de makeFunc . Se crea un nuevo valor de esta variable local cada vez que se llama a makeFunc , y el lambda devuelto crea un cierre que “guarda” el valor de la variable local que estaba vigente para esa invocación de makeFunc . Así, cada función creada en el bucle tiene su propia variable “privada” llamada x . (Para este caso simple, esto también se puede hacer usando un lambda para la función externa — stuff = [(lambda n: lambda x: x+n)(n) for n in [1, 2, 3]] – – pero esto es menos legible.)

Tenga en cuenta que todavía tiene que pasar su n como argumento, es solo que, al hacerlo de esta manera, no lo pasa como un argumento a la misma función que termina yendo a la lista de stuff ; en lugar de eso, lo pasas como un argumento a una función auxiliar que crea la función que quieres poner en stuff . La ventaja de utilizar este enfoque de dos funciones es que la función devuelta está “limpia” y no tiene el argumento adicional; esto podría ser útil si estuviera ajustando funciones que aceptaron muchos argumentos, en cuyo caso podría resultar confuso recordar dónde estaba el argumento n en la lista. La desventaja es que, al hacerlo de esta manera, el proceso de creación de las funciones es más complicado, ya que necesita otra función de cierre.

El resultado final es que hay una compensación: puede hacer que el proceso de creación de funciones sea más simple (es decir, no es necesario que haya dos funciones anidadas), pero luego debe hacer que la función resultante sea un poco más complicada (es decir, tiene este extra n=n argumento). O puede simplificar la función (es decir, no tiene un argumento n= n), pero luego debe hacer que el proceso de creación de funciones sea más complicado (es decir, necesita dos funciones anidadas para implementar el mecanismo).