Подтвердить что ты не робот

Привязка к локальным значениям Python lambda

Следующий код дважды выливает 1, я ожидаю увидеть 0, а затем 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()

Я ожидал, что python lambdas привяжется к ссылке, на которую указывает локальная переменная, за сценой. Однако, похоже, это не так. Я рассмотрел эту проблему в большой системе, где лямбда делает современный С++ equavalent связывания (например, boost: bind), где в таком случае вы привязываетесь к умному ptr или копируете, чтобы скопировать копию для лямбда.

Итак, как мне привязать локальную переменную к лямбда-функции и сохранить ли она правильную ссылку при ее использовании? Я довольно расстроен поведением, так как я не ожидал этого на языке с сборщиком мусора.

Этот код выглядит следующим образом (l3_e - это переменная, вызывающая проблему):

 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
4b9b3361

Ответ 1

Измените x.append(lambda : pv(v)) на x.append(lambda v=v: pv(v)).

Вы ожидаете, что "python lambdas свяжется с ссылкой, на которую указывает локальная переменная, за сценой", но это не так, как работает Python. Python ищет имя переменной во время вызова функции, а не когда она создается. Использование аргумента по умолчанию работает, потому что аргументы по умолчанию оцениваются при создании функции, а не при ее вызове.

Это не что-то особенное в лямбдах. Рассмотрим:

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

печатает

after foo was defined

Ответ 2

Закрытие лямбда содержит ссылку на используемую переменную, а не ее значение, поэтому, если значение переменной позже изменяется, значение в закрытии также изменяется. То есть значение переменной закрытия разрешается при вызове функции, а не при ее создании. (Поведение Python здесь не является чем-то необычным в мире функционального программирования, для чего оно стоит.)

Существует два решения:

  • Используйте аргумент по умолчанию, привязывая текущее значение переменной к локальному имени во время определения. lambda v=v: pv(v)

  • Используйте двойную лямбду и сразу вызывайте первый. (lambda v: lambda: pv(v))(v)