Если я сделаю два списка функций:
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 лямбда-выражений. Например,
i = 13
print b[3]()
В функции makeFun лямбда-выражение закрывается на i, которое определено при вызове функции. Это десять разных i.
Как заявляли другие, проблема заключается в области охвата. Обратите внимание, что вы можете решить эту проблему, добавив дополнительный аргумент в лямбда-выражение и присвоив ему значение по умолчанию:
>> 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, которая является функцией без аргументов. 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
Успех !
Лямбда-выражения в 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)
с тем же результатом.
b[2]() также возвращает 9.
- person Virgil Dupras; 03.12.2009
for облегчат понимание того, откуда берется i.
- person Joe Koberg; 03.12.2009