Enquanto eu estava investigando um problema que tive com fechamentos lexicais no código Javascript, me deparei com esse problema no Python:
flist = []
for i in xrange(3):
def func(x): return x * i
flist.append(func)
for f in flist:
print f(2)
Observe que este exemplo evita conscientemente lambda
. Imprime "4 4 4", o que é surpreendente. Eu esperaria "0 2 4".
Esse código Perl equivalente faz o certo:
my @flist = ();
foreach my $i (0 .. 2)
{
push(@flist, sub {$i * $_[0]});
}
foreach my $f (@flist)
{
print $f->(2), "\n";
}
"0 2 4" é impresso.
Você pode explicar a diferença?
Atualizar:
O problema não é com i
estar global. Isso exibe o mesmo comportamento:
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)
Como mostra a linha comentada, i
é desconhecido naquele momento. Ainda assim, imprime "4 4 4".
python
closures
lazy-evaluation
late-binding
Eli Bendersky
fonte
fonte
Respostas:
Python está realmente se comportando conforme definido. Três funções separadas são criadas, mas cada uma possui o fechamento do ambiente em que está definida - nesse caso, o ambiente global (ou o ambiente da função externa, se o loop for colocado dentro de outra função). Porém, este é exatamente o problema - nesse ambiente, i é modificado e todos os fechamentos se referem ao mesmo i .
Aqui está a melhor solução que eu posso encontrar - crie um criador de funções e invoque- o . Isso forçará ambientes diferentes para cada uma das funções criadas, com um i diferente em cada uma.
É o que acontece quando você mistura efeitos colaterais e programação funcional.
fonte
def inner(x, i=i): return x * i
As funções definidas no loop continuam acessando a mesma variável
i
enquanto seu valor é alterado. No final do loop, todas as funções apontam para a mesma variável, que mantém o último valor no loop: o efeito é o relatado no exemplo.Para avaliar
i
e usar seu valor, um padrão comum é defini-lo como padrão: parâmetros padrão são avaliados quando adef
instrução é executada e, portanto, o valor da variável de loop é congelado.O seguinte funciona como esperado:
fonte
def
instrução é executada /i
da definição. :-(Veja como você faz isso usando a
functools
biblioteca (que não tenho certeza de que estava disponível no momento em que a pergunta foi feita).Saídas 0 2 4, conforme o esperado.
fonte
functools.partialmethod()
a partir de python 3.4Veja isso:
Isso significa que todos apontam para a mesma instância variável i, que terá o valor 2 quando o loop terminar.
Uma solução legível:
fonte
O que está acontecendo é que a variável i é capturada e as funções estão retornando o valor ao qual está vinculada no momento em que é chamada. Nas linguagens funcionais, esse tipo de situação nunca surge, pois eu não me recuperaria. No entanto, com python, e também como você viu com lisp, isso não é mais verdade.
A diferença no exemplo do seu esquema é a semântica do loop do. O esquema está efetivamente criando uma nova variável i a cada vez no loop, em vez de reutilizar uma ligação i existente, como nos outros idiomas. Se você usar uma variável diferente criada externa ao loop e a modificar, verá o mesmo comportamento no esquema. Tente substituir seu loop por:
Dê uma olhada aqui para uma discussão mais aprofundada sobre isso.
[Editar] Possivelmente, uma maneira melhor de descrevê-lo é pensar no loop do como uma macro que executa as seguintes etapas:
ie o equivalente ao python abaixo:
O i não é mais aquele do escopo pai, mas uma variável nova em seu próprio escopo (ou seja, o parâmetro para o lambda) e, assim, você obtém o comportamento observado. O Python não possui esse novo escopo implícito; portanto, o corpo do loop for apenas compartilha a variável i.
fonte
Ainda não estou totalmente convencido de que, em alguns idiomas, isso funciona de uma maneira e de outra maneira. No Common Lisp é como Python:
Imprime "6 6 6" (observe que aqui a lista é de 1 a 3 e é construída ao contrário "). Enquanto estiver no Scheme, ele funciona como no Perl:
Imprime "6 4 2"
E como já mencionei, o Javascript está no campo Python / CL. Parece que há uma decisão de implementação aqui, que diferentes idiomas abordam de maneiras distintas. Eu adoraria entender qual é a decisão, exatamente.
fonte
O problema é que todas as funções locais se ligam ao mesmo ambiente e, portanto, à mesma
i
variável. A solução (solução alternativa) é criar ambientes separados (quadros de pilha) para cada função (ou lambda):fonte
A variável
i
é uma global, cujo valor é 2 a cada vez que a funçãof
é chamada.Eu estaria inclinado a implementar o comportamento que você procura da seguinte maneira:
Resposta à sua atualização : Não é a globalidade
i
propriamente dita que está causando esse comportamento, é o fato de ser uma variável de um escopo que possui um valor fixo nos momentos em que f é chamado. No seu segundo exemplo, o valor dei
é obtido do escopo dakkk
função e nada está mudando quando você chama as funçõesflist
.fonte
O raciocínio por trás do comportamento já foi explicado e várias soluções foram publicadas, mas acho que essa é a mais pitônica (lembre-se, tudo em Python é um objeto!):
A resposta de Claudiu é muito boa, usando um gerador de funções, mas a resposta de piro é um hack, para ser honesto, pois está transformando-o em um argumento "oculto" com um valor padrão (funcionará bem, mas não é "pitônico") .
fonte
func
inx * func.i
sempre se refere à última função definida. Portanto, mesmo que cada função individualmente tenha o número correto preso, todas elas acabam lendo a última.