¿Cómo se refiere una función lambda a sus parámetros en python?

Soy nuevo en Python. Mi tarea fue bastante simple: necesito una lista de funciones que puedo usar para hacer cosas en lotes. Así que jugué con algunos ejemplos como

fs = [lambda x: x + i for i in xrange(10)] 

Sorprendentemente, la llamada de

 [f(0) for f in fs] 

Me dio el resultado como [9, 9, 9, 9, 9, 9, 9, 9, 9, 9] . No fue lo que esperaba, ya que me gustaría que la variable i tenga diferentes valores en diferentes funciones.

Así que mi pregunta es:

  1. ¿Es la variable i en lambda global o local?

  2. ¿Python tiene el mismo concepto como ‘cierre’ en javascript? Quiero decir, ¿cada lambda aquí contiene una referencia a la variable i o solo tienen una copia del valor de i en cada una?

  3. ¿Qué debo hacer si quisiera que la salida fuera [0, 1, .....9] en este caso?

Parece un poco desordenado, pero puedes obtener lo que quieres haciendo algo como esto:

 >>> fs = [(lambda y: lambda x: x + y)(i) for i in xrange(10)] >>> [f(0) for f in fs] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 

Normalmente, Python admite el concepto de “cierre” similar al que estás acostumbrado en Javascript. Sin embargo, para este caso particular de una expresión lambda dentro de una lista de comprensión, parece que i solo está enlazado una vez y toma cada valor en sucesión, dejando que cada función devuelta actúe como si i 9. El truco anterior pasa explícitamente a cada el valor de i en un lambda que devuelve otro lambda, utilizando el valor capturado de y .

El problema que está encontrando aquí es la distinción entre “enlace anticipado” y “enlace tardío”.

Cuando Python busca una variable de un ámbito externo ( i en este caso) utiliza un enlace tardío. Eso significa que ve el valor de esa variable en el momento en que se llama a la función, en lugar del valor en el momento en que se define la función.

Entonces, en su código de ejemplo, las 10 funciones lambda ven el valor final asignado a la variable i por el proceso de bucle: 9 .

La respuesta de Greg muestra una forma de forzar el comportamiento de enlace temprano (es decir, crear un cierre adicional y llamarlo inmediatamente mientras está dentro del bucle).

Otro enfoque comúnmente utilizado para forzar la semántica de vinculación temprana es el “hack del argumento predeterminado”, que enlaza la variable como un argumento predeterminado en el momento de la definición de la función:

 >>> fs = [(lambda x, _i=i: x + _i) for i in xrange(10)] >>> [f(0) for f in fs] [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 

Cualquiera de los dos métodos funciona. Greg tiene la ventaja de no alterar la firma de la función devuelta, el argumento predeterminado hack es más rápido y es mucho más legible que agregar un nivel de cierre adicional al definir funciones nombradas en lugar de usar expresiones lambda.

  1. La variable i es local a la lista de comprensión, pero está disponible para la lambda porque la lambda está en su scope.
  2. Sí, las lambdas son cierres. Es posible que el enlace variable no siempre funcione de la manera deseada, pero son cierres. Sin embargo, no debes confiar mucho en ellos.
  3. Solo querrías xrange(10) sobre xrange(10) . Puedes hacer esto con las lambdas (ver la otra respuesta), pero no querrías hacerlo. Lambdas se debe utilizar con bastante moderación.

La razón por la que la lambda se comporta de esta manera es porque para cada bucle i es rebote. Como no soy local para la lambda, también cambia, y el último valor que tiene es 9. Así que todo lo que estás haciendo es 0 + 9 10 veces.

i es local y de hecho hay cierres en python.

Creo que su confusión es que suscribe una lista de funciones idénticas.

 >>> fs = [lambda x: x + i for i in xrange(10)] >>> fs [ at 0x02C6E930>,  at 0x02C6E970>,  at 0x02C6E9B0>,  at 0x02C6E9F0>,  at 0x02C6EA30>,  at 0x02C6EA70>,  at 0x02C6EAB0>,  at 0x02C6EAF0>,  at 0x02C6EB30>,  at 0x02C6EB70>] >>> fs[0](0) 9 >>> fs[0](100) 109 >>> fs[5](0) 9 >>> fs[5](100) 109 

Creo que una sola función que devuelve una lista sería más apropiada.

 >>> fs3 = lambda x: [x + i for i in xrange(10)] >>> fs3  at 0x02C6EC70> >>> fs3(0) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]