2010-04-28 11 views
6

Ho lavorato su un framework di test di base per una build automatizzata. La parte di codice seguente rappresenta un semplice test di comunicazione tra due macchine che utilizzano programmi diversi. Prima di eseguire qualsiasi test, voglio definirli completamente, quindi questo test non verrà eseguito fino a quando non saranno stati dichiarati tutti i test. Questo pezzo di codice è semplicemente una dichiarazione di un test.Lambdate Python e attacchi variabili

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) 

In ogni caso, dopo l'esecuzione del test, viene eseguita la funzione definita da "passIf". Dal momento che voglio eseguire questo test per più client, li sto iterando e definendo un test per ciascuno: non un grosso problema. Tuttavia, dopo aver eseguito il test sul primo client, il "passIf" valuta l'ultimo nell'elenco dei client, non il "client" al momento della dichiarazione lambda.

La mia domanda, allora: quando Python lega i riferimenti variabili in lambda? Ho pensato che se usare una variabile esterna a lambda non fosse legale, l'interprete non avrebbe idea di cosa stavo parlando. Invece, si collegava in silenzio all'istanza dell'ultimo 'client'.

Inoltre, c'è un modo per forzare la risoluzione nel modo in cui l'ho intesa?

risposta

7

La variabile client è definita nell'ambito esterno, pertanto quando si esegue lambda verrà sempre impostato sull'ultimo client nell'elenco.

per ottenere il risultato voluto, si può dare il lambda una discussione con un valore di default:

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

Dal momento che il valore predefinito è valutata al momento della lambda è definito, il suo valore rimarrà corretta.

Un altro modo è quello di creare il lambda all'interno di una funzione:

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

Qui il lambda riferisce alla variabile client nella funzione createLambda, che ha il valore corretto.

+0

L'utilizzo del valore predefinito funziona perfettamente. Grazie! – stringer

5

Quello che succede è che l'argomento passIf, il lambda, si riferisce alla variabile client dall'ambito di inclusione. Non si riferisce all'oggetto alla variabile client si riferisce a quando viene creata, ma alla variabile stessa. Se chiamate questi passIf dopo che il ciclo è terminato, significa che si riferiscono tutti all'ultimo valore nel ciclo. (Nella terminologia chiusura, chiusure di Python sono late-binding, non presto vincolante.)

Fortunatamente è abbastanza facile da fare una chiusura late-binding in una chiusura anticipata vincolante. Puoi farlo semplicemente dando il lambda una discussione con come predefinito il valore che si desidera associare:

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

Che cosa significa la funzione ottiene tale argomento in più, e potrebbe rovinare le cose, se viene chiamato con un argomento per errore - o quando vuoi che la funzione assuma argomenti arbitrari. Quindi un'altra tecnica è farlo in questo modo:

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

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

Bella spiegazione. Sento che questo rende un po 'più chiaro :). "Non si riferisce all'oggetto a cui fa riferimento la variabile client quando viene creata, ma la variabile stessa." ** e poiché la variabile client è mutabile, alla fine del ciclo punta verso l'ultimo oggetto "client". Quindi, quel valore persiste. ** – narayan