Странное поведение лямбда в цикле

Я наткнулся на поведение на питоне, которое мне трудно понять. Это код доказательной концепции:

from functools import partial if __name__ == '__main__': sequence = ['foo', 'bar', 'spam'] loop_one = lambda seq: [lambda: el for el in seq] no_op = lambda x: x loop_two = lambda seq: [partial(no_op, el) for el in seq] for func in (loop_one, loop_two): print [f() for f in func(sequence)] 

Вышеуказанный результат:

 ['spam', 'spam', 'spam'] ['foo', 'bar', 'spam'] 

Поведение loop_one удивительно для меня, так как я ожидаю, что он будет вести себя как loop_two : el – неизменяемое значение (строка), которое изменяется в каждом цикле, но lambda похоже, сохраняет указатель на «циклическую переменную» , например, если loop будет перерабатывать один и тот же адрес памяти для каждого элемента последовательности.

Вышеупомянутое поведение одинаково с полномасштабными функциями с циклом for в них (поэтому он не является синтаксисом для определения списка).

Но подождите: есть еще … и более загадочно!

Следующий скрипт работает как loop_one :

 b = [] for foo in ("foo", "bar"): b.append(lambda: foo) print [a() for a in b] 

(вывод: ['bar', 'bar'] )

Но наблюдайте, что происходит, когда вы заменяете имя переменной foo a :

 b = [] for a in ("foo", "bar"): b.append(lambda: a) print [a() for a in b] 

(вывод: [<function <lambda> at 0x25cce60>, <function <lambda> at 0x25cced8>] )

Любая идея о том, что здесь происходит? Я подозреваю, что должна быть какая-то информация, связанная с базовой реализацией C моего интерпретатора, но у меня нет ничего (Jthon, PyPy или аналогичный), чтобы проверить, является ли это поведение согласованным в разных реализациях.

    2 Solutions collect form web for “Странное поведение лямбда в цикле”

    Функция lambda: el используемая в loop_one относится к переменной el которая не определена в локальной области. Таким образом, Python ищет его далее в области охвата другой lambda :

     lambda seq: [lambda: el for el in seq] 

    в соответствии с так называемым правилом LEGB .

    К тому времени, когда вызывается lambda: el , эта охватывающая лямбда (конечно) уже была вызвана, и понимание списка было оценено. Элемент el используемый в понимании списка, является локальной переменной в этой охватывающей лямбда. Его значением является тот, который возвращается, когда Python ищет значение el в lambda: el . Это значение для el является одинаковым для всех различных функций lambda: el в понимании списка: это последнее значение, присвоенное el for el in seq loop. Таким образом, el всегда является 'spam' , последним значением в seq .


    Вы уже нашли одно обходное решение, чтобы использовать закрытие, например, loop_two . Другой способ – определить el как локальную переменную со значением по умолчанию:

     loop_one = lambda seq: [lambda el=el: el for el in seq] 

    Переменные ( foo в следующем примере) привязываются не при создании лямбда, а при вызове лямбда.

     >>> b = [] >>> for foo in ("foo", "bar"): ... b.append(lambda: foo) ... >>> foo = "spam" >>> print [a() for a in b] ['spam', 'spam'] 

     >>> b = [] >>> for foo in ("foo", "bar"): ... b.append(lambda foo=foo: foo) ... >>> print [a() for a in b] ['foo', 'bar'] 
    Python - лучший язык программирования в мире.