Если я сделаю два списка функций:
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
Если я сделаю два списка функций:
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
Технически, лямбда-выражение закрывается над i
, которое видимо в глобальной области видимости в последнем поле, которое в последний раз установлено равным 9. Это же значение i
относится ко всем 10 lambdas. Например,
i = 13
print b[3]()
В функции makeFun
лямбда закрывается на i
, которая определяется при вызове функции. Это десять разных i
s.
Как утверждали другие, проблема - проблема. Обратите внимание, что это можно решить, добавив дополнительный аргумент в выражение лямбда и присвоив ему значение по умолчанию:
>> 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
.
Один набор функций (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
>>>
Чтобы добавить некоторую ясность (по крайней мере, на мой взгляд)
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
Успех!
Lambdas в python разделяет область видимости переменной, в которой они созданы. В вашем первом случае область лямбда - это makeFun. В вашем втором случае это глобальный i
, который равен 9, потому что он остается от цикла.
Это то, что я понимаю в любом случае...
Хороший улов. Лямбда в понимании списка видит один и тот же локальный i
каждый раз.
Вы можете переписать его как:
a = []
for i in range(10):
a.append(makefun(i))
b = []
for i in range(10):
b.append(lambda: i)
с тем же результатом.