Странное поведение: понимание 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() элементов.
[Примечание: этот ответ неверен; см. комментарии]