web-dev-qa-db-fra.com

Python liaison lambda aux valeurs locales

Le code suivant crache 1 deux fois, je m'attends à voir 0 puis 1

def pv(v) :
  print v


def test() :
  value = []
  value.append(0)
  value.append(1)
  x=[]
  for v in value :
    x.append(lambda : pv(v))
  return x

x = test()
for xx in x:
  xx()

Je m'attendais à python lambdas à lier à la référence vers laquelle une variable locale pointe, derrière la scène. Cependant, cela ne semble pas être le cas. J'ai rencontré ce problème dans un grand système où le lambda fait l'équavalent du C++ moderne d'une liaison ('boost :: bind' par exemple) où dans ce cas vous vous lieriez à un ptr intelligent ou une copie construirait une copie pour le lambda.

Alors, comment lier une variable locale à une fonction lambda et lui faire conserver la référence correcte lorsqu'elle est utilisée? Je suis assez abasourdi par le comportement car je ne m'attendrais pas à cela d'une langue avec un garbage collector.

Le code en question se présente comme suit (l3_e est la variable qui cause le problème):

 for category in cat :
      for l2 in cat[category].entries :
        for l3 in cat[category].entries[l2].entry["sub_entries"] :
          l3_e = cat[category].entries[l2].entry["sub_entries"][l3]
          url = "http://forums.heroesofnewerth.com/" + l3_e.entry["url"]
          self.l4_processing_status[l3_e] = 0
          l3_discovery_requests.append( Request(
            url, callback = lambda response : self.parse_l4(response,l3_e)))
          print l3_e.entry["url"]
    return l3_discovery_requests
55
Hassan Syed

Remplacez x.append(lambda : pv(v)) par x.append(lambda v=v: pv(v)).

Vous vous attendez à ce que "les lambdas python se lient à la référence vers laquelle une variable locale pointe, derrière la scène", mais ce n'est pas ainsi que fonctionne Python. Python ressemble le nom de la variable au moment de l'appel de la fonction, pas lors de sa création. L'utilisation d'un argument par défaut fonctionne car les arguments par défaut sont évalués lors de la création de la fonction, et non lors de son appel.

Ce n'est pas quelque chose de spécial à propos des lambdas. Considérer:

x = "before foo defined"
def foo():
    print x
x = "after foo was defined"
foo()

impressions

after foo was defined
85

La fermeture du lambda contient une référence à la variable utilisée, pas à sa valeur, donc si la valeur de la variable change plus tard, la valeur dans la fermeture change également. En d'autres termes, la valeur de la variable de fermeture est résolue lors de l'appel de la fonction et non lors de sa création. (Le comportement de Python ici n'est pas inhabituel dans le monde de la programmation fonctionnelle, pour ce qu'il vaut.)

Il existe deux solutions:

  1. Utilisez un argument par défaut, liant la valeur actuelle de la variable à un nom local au moment de la définition. lambda v=v: pv(v)

  2. Utilisez un double lambda et appelez immédiatement le premier. (lambda v: lambda: pv(v))(v)

18
kindall