Ok, tenha paciência comigo, eu sei que vai parecer terrivelmente complicado, mas por favor me ajude a entender o que está acontecendo.
from functools import partial
class Cage(object):
def __init__(self, animal):
self.animal = animal
def gotimes(do_the_petting):
do_the_petting()
def get_petters():
for animal in ['cow', 'dog', 'cat']:
cage = Cage(animal)
def pet_function():
print "Mary pets the " + cage.animal + "."
yield (animal, partial(gotimes, pet_function))
funs = list(get_petters())
for name, f in funs:
print name + ":",
f()
Dá:
cow: Mary pets the cat.
dog: Mary pets the cat.
cat: Mary pets the cat.
Então, basicamente, por que não estou recebendo três animais diferentes? Não está cage
'empacotado' no escopo local da função aninhada? Se não, como uma chamada à função aninhada pesquisa as variáveis locais?
Eu sei que encontrar esse tipo de problema geralmente significa que alguém está 'fazendo tudo errado', mas gostaria de entender o que acontece.
for animal in ['cat', 'dog', 'cow']
... Tenho certeza de que alguém vai aparecer e explicar isso - é um daqueles pegadinhas do Python :)Respostas:
A função aninhada procura variáveis do escopo pai quando executada, não quando definida.
O corpo da função é compilado e as variáveis 'livres' (não definidas na própria função por atribuição), são verificadas e, em seguida, vinculadas como células de fechamento à função, com o código usando um índice para fazer referência a cada célula.
pet_function
portanto, tem uma variável livre (cage
) que é então referenciada por meio de uma célula de fechamento, índice 0. O próprio fechamento aponta para a variável localcage
naget_petters
função.Quando você realmente chama a função, esse encerramento é usado para observar o valor de
cage
no escopo circundante no momento em que você chama a função . Aqui está o problema. No momento em que você chama suas funções, aget_petters
função já está computando seus resultados. Acage
variável local em algum momento durante essa execução foi atribuído cada um dos'cow'
,'dog'
e'cat'
cordas, mas no final da função,cage
contém o último valor'cat'
. Assim, quando você chama cada uma das funções retornadas dinamicamente, você obtém o valor'cat'
impresso.A solução é não depender de fechamentos. Você pode usar uma função parcial em vez disso, criar um novo escopo de função ou vincular a variável como um valor padrão para um parâmetro de palavra-chave .
Exemplo de função parcial, usando
functools.partial()
:Criando um novo exemplo de escopo:
Vinculando a variável como um valor padrão para um parâmetro de palavra-chave:
Não há necessidade de definir a
scoped_cage
função no loop, a compilação ocorre apenas uma vez, não em cada iteração do loop.fonte
Meu entendimento é que cage é procurado no namespace da função pai quando a pet_function produzida é realmente chamada, não antes.
Então, quando você faz
Você gera 3 funções que encontrarão a gaiola criada por último.
Se você substituir seu último loop por:
Você realmente obterá:
fonte
Isso decorre do seguinte
depois de iterar o valor de
i
é armazenado lentamente como seu valor final.Como um gerador, a função funcionaria (ou seja, imprimir cada valor por vez), mas ao transformar para uma lista, ela é executada sobre o gerador , portanto, todas as chamadas para
cage
(cage.animal
) retornam gatos.fonte
Vamos simplificar a pergunta. Definir:
Então, assim como na pergunta, temos:
Mas se evitarmos criar um
list()
primeiro:O que está acontecendo? Por que essa diferença sutil muda completamente nossos resultados?
Se olharmos
list(get_petters())
, fica claro pela mudança de endereços de memória que realmente produzimos três funções diferentes:No entanto, dê uma olhada nos
cell
s aos quais essas funções estão vinculadas:Para ambos os loops, o
cell
objeto permanece o mesmo ao longo das iterações. No entanto, como esperado, astr
referência específica varia no segundo loop. Ocell
objeto se refere aanimal
, que é criado quandoget_petters()
é chamado. No entanto,animal
muda a qualstr
objeto ele se refere quando a função do gerador é executada .No primeiro loop, durante cada iteração, criamos todos os
f
s, mas só os chamamos depois que o geradorget_petters()
está completamente esgotado e umlist
de funções já tiver sido criado.No segundo loop, durante cada iteração, estamos pausando o
get_petters()
gerador e chamandof
após cada pausa. Assim, acabamos recuperando o valor deanimal
naquele momento no tempo em que a função do gerador está pausada.Como @Claudiu responde a uma pergunta semelhante :
fonte