2011-05-17 22 views
11

Cuando ejecuto el siguiente script, ambos lambda ejecutan os.startfile() en el mismo archivo - junk.txt. Esperaría que cada lambda utilizara el valor "f" cuando se creó la lambda. ¿Hay alguna manera de hacer que esto funcione como espero?El cierre de Python no funciona como se esperaba

import os 


def main(): 
    files = [r'C:\_local\test.txt', r'C:\_local\junk.txt'] 
    funcs = [] 
    for f in files: 
     funcs.append(lambda: os.startfile(f)) 
    print funcs 
    funcs[0]() 
    funcs[1]() 


if __name__ == '__main__': 
    main() 
+0

similar a [generar funciones dentro del ciclo con la expresión lambda en python] (http://stackoverflow.com/questions/1841268/generating-functions-inside-loop-with-lambda-expression-in-python) – Rodrigue

Respuesta

22

Una forma es hacer esto:

def main(): 
    files = [r'C:\_local\test.txt', r'C:\_local\junk.txt'] 
    funcs = [] 
    for f in files: 
     # create a new lambda and store the current `f` as default to `path` 
     funcs.append(lambda path=f: os.stat(path)) 
    print funcs 

    # calling the lambda without a parameter uses the default value 
    funcs[0]() 
    funcs[1]() 

De lo contrario f se busca cuando la función es llamada, por lo que se obtiene el valor actual (después del bucle).

maneras me gusta más:

def make_statfunc(f): 
    return lambda: os.stat(f) 

for f in files: 
    # pass the current f to another function 
    funcs.append(make_statfunc(f)) 

o incluso (en Python 2.5 +):

from functools import partial 
for f in files: 
    # create a partially applied function 
    funcs.append(partial(os.stat, f)) 
+7

Esto funciona porque los argumentos predeterminados están vinculados al tiempo de definición de la función. – delnan

+0

Ah, increíble. Las lambdas generadas se están usando realmente como controladores de eventos para PyQt, por lo que no pueden tener ningún parámetro de entrada, y actualmente estoy atrapado en python 2.4, así que no puedo usar functools, sino la segunda solución, pasándola a otra función - funciona perfecto. Gracias. –

+0

@Brendan, si define ranuras qt, debería poder tener parámetros de entrada con valores predeterminados, ¿no? También es posible que desee aplicar el decorador "PyQt4.QtCore.pyqtSlot" si elige la segunda solución. –

4

Es importante entender que cuando una variable se convierte en parte de un cierre que es la propia variable y no el valor está incluido.

Esto significa que todos los cierres creados en el bucle utilizan la misma variable f, que al final del bucle contendrá el último valor utilizado dentro del bucle.

Debido a cómo el lenguaje se define sin embargo esas variables son capturados "sólo lectura" en Python 2.x: ninguna asignación crea una variable de una local a menos que sea declarado global (3.x Python añade la palabra clave nonlocal para permitir la escritura de un local del alcance externo).

Como dijo Jochen Ritzel en su respuesta el lenguaje común para evitar esta variable captura y obtener su lugar de captura de valor es escribir

lambda f=f: os.startfile(f) 

esto funciona porque los valores de los parámetros por defecto son evaluados durante la creación de la función, y f es no la variable externa sino un parámetro de función que tendrá el valor que desea como predeterminado (por lo que esta lambda es solo una función con valores predeterminados para los parámetros, sin cerrar ninguna variable léxica).

Cuestiones relacionadas