2010-04-28 7 views
6

J'ai travaillé sur un framework de test de base pour une construction automatisée. Le code ci-dessous représente un simple test de communication entre deux machines utilisant des programmes différents. Avant de faire des tests, je veux les définir complètement. Donc, ce test ne sera pas exécuté avant que tous les tests aient été déclarés. Ce morceau de code est simplement une déclaration d'un test.Lambda et liaisons de variables python

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 toute façon, après l'exécution du test, il exécute la fonction définie par 'passIf'. Puisque je veux faire ce test pour plusieurs clients, je les réitère et je définis un test pour chacun - pas de problème. Cependant, après avoir exécuté le test sur le premier client, le 'passIf' évalue le dernier dans la liste des clients, pas le 'client' au moment de la déclaration lambda.

Ma question, alors: quand python lie-t-il des références de variable dans lambdas? Je me suis dit que si l'utilisation d'une variable de l'extérieur du lambda n'était pas légale, l'interprète n'aurait aucune idée de ce dont je parlais. Au lieu de cela, il s'est lié en silence à l'instance du dernier «client».

De même, existe-t-il un moyen de forcer la résolution comme je l'ai voulu?

Répondre

7

La variable client est définie dans la portée externe. Par conséquent, au moment de l'exécution de lambda, elle sera toujours définie sur le dernier client de la liste.

Pour obtenir le résultat escompté, vous pouvez donner le lambda un argument avec une valeur par défaut:

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

Puisque la valeur par défaut est évaluée au moment où le lambda est défini, sa valeur reste correcte.

Une autre façon est de créer le lambda dans une fonction:

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

Ici, le lambda se réfère à la variable client dans la fonction createLambda, qui a la valeur correcte.

+0

L'utilisation de la valeur par défaut fonctionne parfaitement. Merci! – stringer

5

Qu'est-ce qui se passe est que votre argument de passIf, lambda, fait référence à la variable client du champ d'application englobante. Il ne fait pas référence à l'objet auquel la variable client fait référence lorsqu'il est créé, mais à la variable elle-même. Si vous appelez ces passIf après la fin de la boucle, cela signifie qu'ils se réfèrent tous à la dernière valeur de la boucle. (Dans la terminologie de fermeture, les fermetures de Python sont liaison tardive, non liaison précoce.)

Heureusement, il est assez facile de faire une fermeture liaison tardive dans une fermeture anticipée obligatoire. Vous pouvez le faire en donnant simplement le lambda un argument avec comme valeur par défaut la valeur que vous voulez lier:

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

Cela ne signifie la fonction obtient cet argument supplémentaire, et pourrait les choses gâcher si elle est appelée avec un argument par accident - ou lorsque vous voulez que la fonction prenne des arguments arbitraires. Donc, une autre technique est de le faire comme ceci:

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

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

Explication de Nice. Je pense que cela le rend un peu plus clair :). "Il ne fait pas référence à l'objet auquel la variable client fait référence lorsqu'elle est créée, mais à la variable elle-même." ** et puisque la variable client est mutable, à la fin de la boucle, elle pointe vers le dernier objet 'client'. Par conséquent, cette valeur persiste. ** – narayan