Странное поведение: понимание Lambda внутри списка

В python 2.6:

[x() for x in [lambda: m for m in [1,2,3]]]

приводит к:

[3, 3, 3]

Я ожидаю, что выход будет [1, 2, 3]. Я получаю точно такую ​​же проблему даже при использовании подхода, не учитывающего список. И даже после того, как я копирую m в другую переменную.

Что мне не хватает?

Ответы

Ответ 1

Чтобы lambdas помнил значение m, вы можете использовать аргумент со значением по умолчанию:

[x() for x in [lambda m=m: m for m in [1,2,3]]]
# [1, 2, 3]

Это работает, потому что значения по умолчанию устанавливаются один раз во время определения. Каждая лямбда теперь использует свое собственное значение по умолчанию m вместо поиска значения m во внешней области во время выполнения лямбда.

Ответ 2

Эффект, с которым вы сталкиваетесь, называется закрытия, когда вы определяете функцию, которая ссылается на нелокальные переменные, функция сохраняет ссылку на переменную, а не получать свою собственную копию. Чтобы проиллюстрировать это, Ill расширит ваш код в эквивалентную версию без понимания или lambdas.

inner_list = []
for m in [1, 2, 3]:
    def Lambda():
         return m
    inner_list.append(Lambda)

Итак, в этот момент inner_list имеет три функции в нем, и каждая функция при вызове возвращает значение m. Но главное, что все они видят тот же самый m, хотя m меняется, они никогда не смотрят на него, пока его не назовут намного позже.

outer_list = []
for x in inner_list:
    outer_list.append(x())

В частности, поскольку внутренний список создается полностью до того, как внешний список начинает формироваться, m уже достиг своего последнего значения 3, и все три функции видят это же значение.

Ответ 3

Короче говоря, вы не хотите этого делать. Более конкретно, то, с чем вы сталкиваетесь, - это проблема порядка операций. Вы создаете три отдельных lambda, которые возвращают m, но ни один из них не вызывается немедленно. Затем, когда вы получаете понимание внешнего списка, и все они называются остаточным значением m, равно 3, последнее значение внутреннего понимания списка.

- Для комментариев -

>>> [lambda: m for m in range(3)]
[<function <lambda> at 0x021EA230>, <function <lambda> at 0x021EA1F0>, <function <lambda> at 0x021EA270>]

Это три отдельных лямбда.

И, как еще одно доказательство:

>>> [id(m) for m in [lambda: m for m in range(3)]]
[35563248, 35563184, 35563312]

Опять же, три отдельных идентификатора.

Ответ 4

Посмотрите на __closure__ функций. Все 3 указывают на один и тот же объект ячейки, который сохраняет ссылку на m из внешней области:

>>> print(*[x.__closure__[0] for x in [lambda: m for m in [1,2,3]]], sep='\n')
<cell at 0x00D17610: int object at 0x1E2139A8>
<cell at 0x00D17610: int object at 0x1E2139A8>
<cell at 0x00D17610: int object at 0x1E2139A8>

Если вы не хотите, чтобы ваши функции принимали m в качестве аргумента ключевого слова, в ответ на unubtu вы могли вместо этого использовать дополнительную лямбду для оценки m на каждой итерации:

>>> [x() for x in [(lambda x: lambda: x)(m) for m in [1,2,3]]]
[1, 2, 3]

Ответ 5

Лично я считаю это более элегантным решением. Lambda возвращает функцию, поэтому, если мы хотим использовать эту функцию, мы должны ее использовать. Сложно использовать один и тот же символ для "анонимной" переменной в лямбда и для генератора, поэтому в моем примере я использую другой символ, чтобы сделать его более ясным.

>>> [ (lambda a:a)(i) for i in range(3)]
[0, 1, 2]
>>> 

и быстрее.

>>> timeit.timeit('[(lambda a:a)(i) for i in range(10000)]',number=10000)
9.231263160705566
>>> timeit.timeit('[lambda a=i:a  for i in range(10000)]',number=10000)
11.117988109588623
>>> 

но не так быстро, как карта:

>>> timeit.timeit('map(lambda a:a,  range(10000))',number=10000)
5.746963977813721

(Я запускал эти тесты более одного раза, результат был таким же, это было сделано в python 2.7, результаты отличаются в python 3: два понимания списка намного ближе к производительности, и они намного медленнее, карта остается намного быстрее.)

Ответ 6

Я тоже это заметил. Я пришел к выводу, что лямбда создается только один раз. Таким образом, на самом деле ваше внутреннее понимание списка даст 3 indentical функции, связанные с последним значением m.

Попробуйте и проверьте id() элементов.

[Примечание: этот ответ неверен; см. комментарии]