Как лямбда-функция ссылается на свои параметры в python? - PullRequest
6 голосов
/ 04 марта 2011

Я новичок в Python.Моя задача была довольно проста - мне нужен список функций, которые я могу использовать для выполнения задач в пакетном режиме.Поэтому я поэкспериментировал с некоторыми примерами, такими как

fs = [lambda x: x + i for i in xrange(10)]

Удивительно, но вызов

[f(0) for f in fs]

дал мне результат, подобный [9, 9, 9, 9, 9, 9, 9, 9, 9, 9].Это было не то, что я ожидал, так как я хотел бы, чтобы переменная i имела разные значения в разных функциях.

Итак, мой вопрос:

  1. Является ли переменная i в лямбдах глобальных или локальных?

  2. Имеет ли python такую ​​же концепцию, как «замыкание» в javascript?Я имею в виду, что каждая лямбда содержит ссылку на переменную i, или они просто содержат копию значения i в каждой?

  3. Что мне делать, если я 'мне бы хотелось, чтобы результат был [0, 1, .....9] в этом случае?

Ответы [ 4 ]

10 голосов
/ 04 марта 2011

Это выглядит немного грязно, но вы можете получить то, что вы хотите, выполнив что-то вроде этого:

>>> fs = [(lambda y: lambda x: x + y)(i) for i in xrange(10)]
>>> [f(0) for f in fs]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Обычно Python поддерживает концепцию "закрытия", аналогичную той, к которой вы привыкли в Javascript.Тем не менее, для этого конкретного случая лямбда-выражения внутри понимания списка кажется, что i ограничивается только один раз и принимает каждое значение подряд, в результате чего каждая возвращаемая функция действует как i равно 9. Вышеуказанный хак явно передает каждое значение i в лямбду, которая возвращает другую лямбду, используя захваченное значение y.

6 голосов
/ 04 марта 2011

Проблема, с которой вы здесь сталкиваетесь, заключается в различии между «ранним связыванием» и «поздним связыванием».

Когда Python ищет переменную из внешней области видимости (в данном случае i), он используетПозднее связывание.Это означает, что он видит значение этой переменной в тот момент, когда функция называется , а не значение во время определения функции.

Итак, в вашем примере кода все 10Лямбда-функции видят окончательное значение, присвоенное переменной i в циклическом процессе: 9.

Ответ Грега показывает один из способов принудительного раннего связывания (т. е. создать дополнительное замыкание и вызвать его немедленно, пока ещевнутри цикла).

Другим часто используемым подходом к форсированию семантики раннего связывания является «взлом аргумента по умолчанию», который связывает переменную в качестве аргумента по умолчанию во время определения функции:

>>> fs = [(lambda x, _i=i: x + _i) for i in xrange(10)]
>>> [f(0) for f in fs]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

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

4 голосов
/ 04 марта 2011
  1. Переменная i является локальной для понимания списка, но она доступна для лямбды, потому что лямбда находится в ее области действия.
  2. Да, лямбды являются замыканиями.Привязка переменных может не всегда работать так, как вы хотите, но это замыкания.Вы не должны сильно на них полагаться.
  3. Вы бы просто хотели перебрать xrange(10).Вы можете сделать это с лямбдами (см. Другой ответ), но вы не захотите.Лямбды следует использовать довольно экономно.

Причина, по которой лямбда ведет себя таким образом, заключается в том, что для каждого цикла i выполняется отскок.Поскольку я не являюсь локальным для лямбды, он тоже меняется, и последнее значение, которое он содержит, равно 9. Итак, все, что вы делаете, это 0 + 9 10 раз.

1 голос
/ 04 марта 2011

Я локальный, и в Python действительно есть замыкания.

Я полагаю, что вы путаетесь в том, что вы присваиваете fs список идентичных функций.

>>> fs = [lambda x: x + i for i in xrange(10)]
>>> fs
[<function <lambda> at 0x02C6E930>, <function <lambda> at 0x02C6E970>, <function <lambda> at 0x02C6E9B0>, <function <lambda> at 0x02C6E9F0>, <function <lambda> at 0x02C6EA30>, <function <lambda> at 0x02C6EA70>, <function <lambda> at 0x02C6EAB0>, <function <lambda> at 0x02C6EAF0>, <function <lambda> at 0x02C6EB30>, <function <lambda> at 0x02C6EB70>]
>>> fs[0](0)
9
>>> fs[0](100)
109
>>> fs[5](0)
9
>>> fs[5](100)
109

Я думаю, что более подходящей была бы функция, возвращающая список.

>>> fs3 = lambda x: [x + i for i in xrange(10)]
>>> fs3
<function <lambda> at 0x02C6EC70>
>>> fs3(0)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...