Странное поведение: лямбда в понимании списка - PullRequest
17 голосов
/ 10 сентября 2011

В Python 2.6:

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

Результат:

[3, 3, 3]

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

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

Ответы [ 6 ]

15 голосов
/ 10 сентября 2011

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

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

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

6 голосов
/ 10 сентября 2011

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

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, и все три функции видят это же значение.

5 голосов
/ 10 сентября 2011

Короче говоря, вы не хотите этого делать. Более конкретно, то, с чем вы сталкиваетесь, является проблемой порядка операций. Вы создаете три отдельных 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]

Снова три отдельных идентификатора.

3 голосов
/ 10 сентября 2011

Посмотрите на __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]
0 голосов
/ 12 ноября 2017

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

>>> [ (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: два понимания списка намного ближе по производительности и оба намного медленнее, карта остается намного быстрее .)

0 голосов
/ 10 сентября 2011

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

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

[Примечание: этот ответне является правильным;смотрите комментарии]

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...