Захват значения вместо ссылки в лямбда-выражениях

Меня немного удивил этот пример, приведенный Эли Бендерски (http://eli.thegreenplace.net/2015/the-scope-of-index-variables-in-pythons-for-loops/)

>>> def foo():
...     lst = []
...     for i in range(4):
...         lst.append(lambda: i)
...     print([f() for f in lst])
...
>>> foo()
[3, 3, 3, 3]

Но когда я подумал об этом, это имело некоторый смысл — лямбда фиксирует ссылку на i, а не значение i.

Таким образом, способ обойти это следующий:

>>> def foo():
...     lst = []
...     for i in range(4):
...         lst.append((lambda a: lambda: a)(i))
...     print([f() for f in lst])
...
>>> foo()
[0, 1, 2, 3]

Похоже, причина того, что это работает, заключается в том, что когда i предоставляется внешней лямбде, внешняя лямбда создает область видимости и разыменовывает i, устанавливая a в i. Затем возвращаемая внутренняя лямбда содержит ссылку на a.

Это правильное объяснение?


person abhillman    schedule 18.01.2015    source источник
comment
Стоит прочитать: stackoverflow.com/questions/20246523/ и stackoverflow.com/a/14414638/1252759   -  person Jon Clements♦    schedule 19.01.2015


Ответы (3)


Параметр по умолчанию — это еще один способ получить значение:

lst.append(lambda i=i: i)
person bav    schedule 18.01.2015

Похоже, причина того, что это работает, заключается в том, что когда i предоставляется внешней лямбде, внешняя лямбда создает область видимости и разыменовывает i, устанавливая a в i. Затем возвращаемая внутренняя лямбда содержит ссылку на a.

Это правильное объяснение?

Мне это не нравится. Python не проходит по ссылке:

def func(x):
    x = 10

num = 3
func(num)

print num  #=>3

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

Причина, по которой пример работает, заключается в правиле:

Локальные переменные функции уничтожаются после завершения ее выполнения.

К локальным переменным функции относятся ее переменные-параметры. Каждый раз, когда выполняется внешняя лямбда, создается новая переменная «a». В результате каждая внутренняя лямбда закрывается по другой переменной 'a'.

Вы намекали на такое положение дел:

внешняя лямбда создает область

...

лямбда фиксирует ссылку на i, а не на значение i.

Или, как я люблю выражаться.

Замыкание закрывается по переменным, а не по значениям.

Именно так замыкания работают в большинстве языков (за исключением Perl, где замыкания закрываются по значениям).

person 7stud    schedule 18.01.2015

Да, выглядит правильно. Если вы знакомы с javascript и знакомы с замыканиями, вы заметите, насколько они похожи.

Если нет - есть хорошее объяснение по SO относительно закрытия JS, и концепция абсолютно такая же (как и объяснение и даже неправильное и правильное использование).

person Salvador Dali    schedule 18.01.2015
comment
Ага! Я знаком с концепцией закрытия! - person abhillman; 19.01.2015