Пока я исследовал проблему, возникшую у меня с лексическими замыканиями в коде Javascript, я столкнулся с этой проблемой в Python:
flist = []
for i in xrange(3): deffunc(x): return x * i flist.append(func)
for f in flist: print f(2)
Обратите внимание, что в этом примере сознательно избегается lambda. Он выводит "4 4 4", что удивительно. Я бы ожидал "0 2 4".
Этот эквивалентный код Perl делает это правильно:
my @flist = ();
foreach my $i (0 .. 2) { push(@flist, sub {$i * $_[0]}); }
foreach my $f (@flist) { print $f->(2), "\n"; }
выводится "0 2 4".
Не могли бы вы, пожалуйста, объяснить разницу ?
Обновить:
Проблема не в i том, что она глобальная. Это отображает то же поведение:
flist = []
defouter(): for i in xrange(3): definner(x): return x * i flist.append(inner)
outer() #~ print i # commented because it causes an error
for f in flist: print f(2)
Как видно из прокомментированной строки, i на данный момент неизвестно. Тем не менее, он выводит "4 4 4".
Переведено автоматически
Ответ 1
Python на самом деле ведет себя так, как определено. Создаютсятри отдельные функции, но каждая из них имеет замыкание среды, в которой они определены - в данном случае глобальной среды (или внешней среды функции, если цикл размещен внутри другой функции). Однако именно в этом и заключается проблема - в этой среде i изменен, и все замыкания относятся к одному и тому же i.
Вот лучшее решение, которое я могу придумать - создать функцию creater и вызвать это вместо этого. Это принудительно создаст разные среды для каждой из созданных функций, с разными i в каждой из них.
flist = []
for i in xrange(3): deffuncC(j): deffunc(x): return x * j return func flist.append(funcC(i))
for f in flist: print f(2)
Это то, что происходит, когда вы смешиваете побочные эффекты и функциональное программирование.
Ответ 2
Функции, определенные в цикле, продолжают обращаться к одной и той же переменной i при изменении ее значения. В конце цикла все функции указывают на одну и ту же переменную, которая содержит последнее значение в цикле: эффект соответствует тому, что показано в примере.
Чтобы оценить i и использовать его значение, обычным шаблоном является установка его в качестве параметра по умолчанию: значения параметров по умолчанию вычисляются при выполнении def инструкции, и, таким образом, значение переменной цикла замораживается.
Следующее работает так, как ожидалось:
flist = []
for i in xrange(3): deffunc(x, i=i): # the *value* of i is copied in func() environment return x * i flist.append(func)
for f in flist: print f(2)
Ответ 3
Вот как вы это делаете, используя functools библиотеку (которая, я не уверен, была доступна на момент постановки вопроса).
from functools import partial
flist = []
deffunc(i, x): return x * i
for i inrange(3): flist.append(partial(func, i))
for f in flist: print(f(2))
Выводит 0 2 4, как и ожидалось.
Ответ 4
посмотрите на это:
for f in flist: print f.func_closure
(<cell at 0x00C980B0: intobject at 0x009864B4>,) (<cell at 0x00C980B0: intobject at 0x009864B4>,) (<cell at 0x00C980B0: intobject at 0x009864B4>,)
Это означает, что все они указывают на один и тот же экземпляр переменной i, который будет иметь значение 2 после завершения цикла.
Удобочитаемое решение:
for i in xrange(3): defffunc(i): deffunc(x): return x * i return func flist.append(ffunc(i))