2010-04-28 9 views
6

He estado trabajando en un marco de prueba básico para una compilación automatizada. El fragmento de código siguiente representa una prueba simple de comunicación entre dos máquinas que utilizan diferentes programas. Antes de hacer algunas pruebas, quiero definirlas por completo, por lo que la siguiente prueba no se ejecuta hasta que se hayan declarado todas las pruebas. Este fragmento de código es simplemente una declaración de una prueba.Python Lambdas y enlaces de variable

remoteTests = [] 
for client in clients: 
    t = Test(
     name = 'Test ' + str(host) + ' => ' + str(client), 
     cmds = [ 
      host.start(CMD1), 
      client.start(CMD2), 

      host.wait(5), 

      host.stop(CMD1), 
      client.stop(CMD2), 
     ], 
     passIf = lambda : client.returncode(CMD2) == 0 
    ) 
remoteTests.append(t) 

De todos modos, después de ejecutar la prueba, ejecuta la función definida por 'passIf'. Como quiero ejecutar esta prueba para múltiples clientes, los estoy iterando y definiendo una prueba para cada uno, no es gran cosa. Sin embargo, después de ejecutar la prueba en el primer cliente, el 'passIf' se evalúa en el último en la lista de clientes, no en el 'cliente' en el momento de la declaración lambda.

Mi pregunta, entonces, ¿cuándo python enlaza las referencias variables en lambdas? Pensé que si usar una variable del exterior de la lambda no era legal, el intérprete no tendría idea de lo que estaba hablando. En cambio, se atiene silenciosamente a la instancia del último 'cliente'.

Además, ¿hay alguna manera de forzar la resolución de la forma en que lo pretendía?

Respuesta

7

La variable client se define en el ámbito externo, por lo que cuando se ejecuta lambda, siempre se establecerá en el último cliente de la lista.

Para obtener el resultado deseado, se puede dar la lambda de una discusión con un valor por defecto:

passIf = lambda client=client: client.returncode(CMD2) == 0 

Dado que el valor por defecto es evaluado en el momento se define la lambda, su valor seguirá siendo correcta.

Otra forma es la de crear la lambda dentro de una función:

def createLambda(client): 
    return lambda: client.returncode(CMD2) == 0 
#... 
passIf = createLambda(client) 

Aquí el lambda se refiere a la variable client en la función de createLambda, que tiene el valor correcto.

+0

El uso del valor predeterminado funciona perfectamente. ¡Gracias! – stringer

5

Lo que pasa es que su argumento passIf, la lambda, se refiere a la variable de client del ámbito de inclusión. No se refiere al objeto al que se refiere la variable client cuando se crea, sino a la variable misma. Si llama a estos passIf después de que el ciclo haya finalizado, eso significa que todos se refieren al último valor del ciclo. (En la terminología de cierre, cierres de Python son enlace tardío, no de enlace temprano.)

Afortunadamente es bastante fácil de hacer un cierre de unión a altas horas de un cierre de enlace temprano. Puede hacerlo simplemente dando la lambda de una discusión con los predeterminados, el valor que desea enlazar:

passIf = lambda client=client: client.returncode(CMD2) == 0 

Eso no significa que la función consigue que el argumento extra, y el desorden fuerza las cosas si se llama con un argumento por accidente, o cuando desee que la función tome argumentos arbitrarios. Así que otra técnica es hacerlo así:

# Before your loop: 
def make_passIf(client): 
    return lambda: client.returncode(CMD2) == 0 

# In the loop 
t = Test(
    ... 
    passIf = make_passIf(client) 
) 
+0

Agradable Explicación. Siento que esto lo hace un poco más claro :). "No se refiere al objeto al que hace referencia la variable cliente cuando se crea, sino a la variable misma". ** y dado que la variable cliente es mutable, al final del ciclo, apunta al último objeto 'cliente'. Por lo tanto, ese valor persiste. ** – narayan