Почему следующий код всегда выводит 16? - PullRequest
4 голосов
/ 22 декабря 2009
def makeActions():
    acts=[]
    for i in range(5):
        print len(acts)
        acts.append(lambda x: i ** x)
        print acts[i]
    return acts
acts=makeActions()
for i in range(5):
    print acts[i](2)
* +1001 * Выход:
16
16
16
16
16

Ожидаемый результат:

0
1
4
9
16

Ответы [ 4 ]

23 голосов
/ 22 декабря 2009

Потому что i в лямбде, вероятно, не то, что вы ожидаете. Чтобы убедиться в этом, измените код:

acts.append(lambda x: (i, i ** x))

Теперь print сообщает вам значение i:

(4, 16)
(4, 16)
(4, 16)
(4, 16)
(4, 16)

Это означает, что lambda не копирует значение i, но сохраняет ссылку на переменную, поэтому все lambda s видят одно и то же значение. Чтобы это исправить, скопируйте i:

acts.append(lambda x, i=i: (i, i ** x))

Маленький i=i создает локальную копию i внутри lambda.

[ПРАВИТЬ] Теперь, почему это? В версиях Python до 2.1 локальные функции (то есть функции, определенные внутри других функций) не могли видеть переменные в той же области видимости.

def makeActions():
    acts=[]
    for i in range(5):
        print len(acts)
        def f(x):   # <-- Define local function
            return i ** x
        acts.append(f)
        print acts[i]
    return acts

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

Это поведение было исправлено в одной из последних версий Python (2.5, IIRC). В этих старых версиях Python вам нужно написать:

        def f(x, i=i):   # <-- Must copy i
            return i ** x

Поскольку исправление (см. PEP 3104 ), f() может видеть переменные в той же области, поэтому lambda больше не требуется.

6 голосов
/ 22 декабря 2009

Поскольку все лямбда-функции, которые вы создаете, связаны с i, который становится 4 в конце цикла, и, как мы все хорошо знаем, 4 * 4 = 16

чтобы избежать создания ваших функций с использованием вложенной функции (замыкание), например

def makePowerFunc(base):
    def powerFunc(x):
        return base**x
    return powerFunc

def makeActions():
    acts=[]
    for i in range(5):
        acts.append(makePowerFunc(i))

    return acts
acts=makeActions()
for i in range(5):
print acts[i](2)

Выход:

0
1
4
9
16

Существуют и другие способы ее решения, но лучше иметь именованную вложенную функцию, а не лямбду, и с такими замыканиями можно сделать еще много вещей

5 голосов
/ 22 декабря 2009

Это нелогичный или, по крайней мере, менее распространенный синтаксис. Я думаю, вы имели в виду:

acts.append(lambda x, i = i: i ** x)

, который выдаст:

0
1
4
9
16

п. в твоей версии

acts.append(lambda x, i: i ** x)

лямбда-функции были созданы, но все они ссылались на локальный i из цикла, который остановился на i = 4, так что все ваши лямбды говорили: lambda x: 4 ** x, следовательно

for i in range(5):
    print acts[i](2)

напечатает все 16 с.


FFN. сообщение в блоге о сломанной лямбде: http://math.andrej.com/2009/04/09/pythons-lambda-is-broken/

3 голосов
/ 22 декабря 2009

Это явление называется лямбда-связыванием, см. Что такое «лямбда-связывание» в Python?

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