Генерация функций внутри цикла с лямбда-выражением в 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

person Anssi    schedule 03.12.2009    source источник
comment
Возможный дубликат области действия лямбда-функций и их параметров?   -  person Akaisteph7    schedule 12.07.2019


Ответы (6)


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

i = 13
print b[3]()

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

person Jonathan Feinberg    schedule 03.12.2009
comment
Хорошо, теперь это имеет смысл для меня. Спасибо! - person Anssi; 03.12.2009
comment
Начиная с Python 3, списковые включения теперь имеют собственную область видимости. Таким образом, изменение его значения не изменит вывод print(b[3]()). Более подробную информацию о поведении понимания списка можно найти на странице ">здесь - person Akaisteph7; 13.07.2019

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

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

person Stephan202    schedule 03.12.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
>>>
person hughdbrown    schedule 03.12.2009

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

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

Успех !

person Panagiotis Karagiannis    schedule 06.12.2012

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

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

person Virgil Dupras    schedule 03.12.2009

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

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

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

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

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

person Joe Koberg    schedule 03.12.2009
comment
Я не уверен, что ты пытаешься сказать. В вашем примере b[2]() также возвращает 9. - person Virgil Dupras; 03.12.2009
comment
Я говорю, что это эквивалентно пониманию списка; не то, что а и б и то же самое. Я подумал, что прописанные петли for облегчат понимание того, откуда берется i. - person Joe Koberg; 03.12.2009