Генерация функций внутри цикла с лямбда-выражением в python - PullRequest
26 голосов
/ 03 декабря 2009

Если я составлю два списка функций:

def makeFun(i):
    return lambda: i

a = [makeFun(i) for i in range(10)]
b = [lambda: i for i in range(10)]

почему списки a и b не равны?

Например:

>>> a[2]()
2
>>> b[2]()
9

Ответы [ 6 ]

19 голосов
/ 03 декабря 2009

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

>> def makeFun(i): return lambda: i
... 
>>> a = [makeFun(i) for i in range(10)]
>>> b = [lambda: i for i in range(10)]
>>> c = [lambda i=i: i for i in range(10)]  # <-- Observe the use of i=i
>>> a[2](), b[2](), c[2]()
(2, 9, 2)

В результате i теперь явно помещается в область действия, ограниченную выражением lambda.

19 голосов
/ 03 декабря 2009

Технически, лямбда-выражение закрыто над i, видимым в глобальной области видимости, для которого в последний раз установлено значение 9. Это то же самое i, к которому относится всего 10 лямбд. Например,

i = 13
print b[3]()

В функции makeFun лямбда закрывается на i, который определяется при вызове функции. Это десять разных i с.

7 голосов
/ 03 декабря 2009

Один набор функций (a) работает с переданным аргументом, а другой (b) - с глобальной переменной, которая затем устанавливается на 9. Проверьте разборку:

>>> import dis
>>> dis.dis(a[2])
  1           0 LOAD_DEREF               0 (i)
              3 RETURN_VALUE
>>> dis.dis(b[2])
  1           0 LOAD_GLOBAL              0 (i)
              3 RETURN_VALUE
>>>
3 голосов
/ 06 декабря 2012

Чтобы добавить ясности (по крайней мере, на мой взгляд)

def makeFun(i): return lambda: i
a = [makeFun(i) for i in range(10)]
b = [lambda: i for i in range(10)]

a использует makeFun (i), который является функцией с аргументом.

b использует лямбду: i, которая является функцией без аргументов. I, который он использует, сильно отличается от предыдущего

Чтобы сделать a и b равными, мы можем сделать обе функции без аргументов:

def makeFun(): return lambda: i
a = [makeFun() for i in range(10)]
b = [lambda: i for i in range(10)]

Теперь обе функции используют глобальный i

.
>>> a[2]()
9
>>> b[2]()
9
>>> i=13
>>> a[2]()
13
>>> b[2]()
13

Или (более полезно) заставить оба использовать один аргумент:

def makeFun(x): return lambda: x
a = [makeFun(i) for i in range(10)]
b = [lambda x=i: x for i in range(10)]

Я намеренно изменил i на x, где переменная является локальной. Сейчас:

>>> a[2]()
2
>>> b[2]()
2

Удачи!

1 голос
/ 03 декабря 2009

Хороший улов. Лямбда в понимании списка каждый раз видит один и тот же локальный i.

Вы можете переписать его как:

a = []
for i in range(10):
    a.append(makefun(i))

b = []
for i in range(10):
    b.append(lambda: i)

с тем же результатом.

1 голос
/ 03 декабря 2009

Лямбды в python совместно используют переменную область, в которой они созданы. В вашем первом случае область действия лямбды - это makeFun. Во втором случае это глобальный i, то есть 9, потому что это остаток от цикла.

Вот что я все равно понимаю ...

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