Пока я исследовал проблему с лексическими замыканиями в коде Javascript, я столкнулся с этой проблемой в Python:
flist = []
for i in xrange(3):
def func(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 = []
def outer():
for i in xrange(3):
def inner(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».
python
closures
lazy-evaluation
late-binding
Эли Бендерский
источник
источник
Ответы:
Python на самом деле ведет себя как определено. Создаются три отдельные функции , но каждая из них имеет замыкание среды, в которой они определены, - в данном случае, глобальной среды (или среды внешней функции, если цикл размещен внутри другой функции). Это как раз и есть проблема - в этой среде я мутирован , и все замыкания ссылаются на одно и то же i .
Вот лучшее решение , которое я могу придумать - создать функцию созидатель и вызов , что вместо этого. Это заставит разные среды для каждой из созданных функций, с разными i в каждой.
Это то, что происходит, когда вы смешиваете побочные эффекты и функциональное программирование.
источник
def inner(x, i=i): return x * i
Функции, определенные в цикле, продолжают обращаться к одной и той же переменной,
i
пока ее значение не изменится. В конце цикла все функции указывают на одну и ту же переменную, которая содержит последнее значение в цикле: эффект - это то, что сообщается в примере.Чтобы оценить
i
и использовать его значение, общий шаблон должен установить его как параметр по умолчанию: значения параметров по умолчанию оцениваются, когда выполняетсяdef
инструкция, и, таким образом, значение переменной цикла замораживается.Следующее работает как ожидалось:
источник
def
оператора /i
из определения. :-(Вот как вы это делаете, используя
functools
библиотеку (которая, я не уверен, была доступна на момент постановки вопроса).Выходы 0 2 4, как и ожидалось.
источник
functools.partialmethod()
в Python 3.4посмотри на это:
Это означает, что все они указывают на один и тот же экземпляр переменной i, который после завершения цикла будет иметь значение 2.
Читаемое решение:
источник
Что происходит, так это то, что переменная i захвачена, и функции возвращают значение, к которому она привязана во время ее вызова. На функциональных языках такого рода ситуация никогда не возникает, так как я бы не вернулся. Однако с python, а также, как вы видели с lisp, это больше не так.
Разница с вашим примером схемы состоит в том, чтобы делать с семантикой цикла do. Схема эффективно создает новую переменную i каждый раз в цикле, а не повторно использует существующую привязку i, как в других языках. Если вы используете другую переменную, созданную вне цикла, и измените ее, вы увидите то же поведение в схеме. Попробуйте заменить ваш цикл на:
Посмотрите здесь для дальнейшего обсуждения этого.
[Edit] Возможно, лучший способ описать это - думать о цикле do как о макросе, который выполняет следующие шаги:
то есть. эквивалент приведенного ниже питона:
I больше не тот из родительской области видимости, а совершенно новая переменная в своей области (т. Е. Параметр лямбда-выражения), и поэтому вы получаете поведение, которое вы наблюдаете. В Python нет этой неявной новой области видимости, поэтому тело цикла for просто использует переменную i.
источник
Я до сих пор не до конца убежден, почему в некоторых языках это работает по-другому, а по-другому. В Common Lisp это похоже на Python:
Выводит «6 6 6» (обратите внимание, что здесь список от 1 до 3 и построен в обратном порядке »). В то время как в схеме он работает как в Perl:
Принты "6 4 2"
И, как я уже упоминал, Javascript находится в лагере Python / CL. Похоже, здесь есть решение о реализации, к которому разные языки подходят по-разному. Я хотел бы понять, какое именно решение, точно.
источник
Проблема в том, что все локальные функции связаны с одной и той же средой и, следовательно, с одной и той же
i
переменной. Решение (обходной путь) заключается в создании отдельных сред (стековых фреймов) для каждой функции (или лямбда):источник
Переменная
i
является глобальной, значение которой равно 2 при каждомf
вызове функции .Я был бы склонен реализовать ваше поведение следующим образом:
Ответ на ваше обновление : это не глобальность
i
как таковая, которая вызывает такое поведение, это факт, что это переменная из охватывающей области, которая имеет фиксированное значение во время вызова f. Во втором примере значениеi
берется из области действияkkk
функции, и при вызове функций ничего не меняетсяflist
.источник
Причины такого поведения уже были объяснены, и было опубликовано несколько решений, но я думаю, что это самое питоническое (помните, что все в Python является объектом!):
Ответ Клаудиу довольно хороший, с использованием генератора функций, но, честно говоря, ответ Пиро - хак, поскольку он превращает меня в «скрытый» аргумент со значением по умолчанию (он будет работать нормально, но не «питонно») ,
источник
func
Вx * func.i
всегда будет относиться к последней функции , определенной. Таким образом, даже несмотря на то, что каждая функция в отдельности имеет правильный номер, она все равно заканчивает тем, что читает из последней.