Привязка к локальным значениям 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
Ответы
Ответ 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)