Подтвердить что ты не робот

Генерирование функций внутри цикла с выражением лямбда в python

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

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
4b9b3361

Ответ 1

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

i = 13
print b[3]()

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

Ответ 2

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

>> 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.

Ответ 3

Один набор функций (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
>>>

Ответ 4

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

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 использует lambda: 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)]

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

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

Успех!

Ответ 5

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

Это то, что я понимаю в любом случае...

Ответ 6

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

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

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

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

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