Recentemente, comecei a brincar com o Python e descobri algo peculiar na maneira como os fechamentos funcionam. Considere o seguinte código:
adders=[0,1,2,3]
for i in [0,1,2,3]:
adders[i]=lambda a: i+a
print adders[1](3)
Ele cria uma matriz simples de funções que recebem uma única entrada e retornam essa entrada adicionada por um número. As funções são construídas em for
loop onde o iterador i
é executado de 0
para 3
. Para cada um desses números lambda
, é criada uma função que captura i
e a adiciona à entrada da função. A última linha chama a segunda lambda
função com 3
como parâmetro. Para minha surpresa, a saída foi 6
.
Eu esperava um 4
. Meu raciocínio era: no Python tudo é um objeto e, portanto, toda variável é um ponteiro essencial para ele. Ao criar os lambda
fechamentos para i
, eu esperava que ele armazenasse um ponteiro para o objeto inteiro atualmente apontado por i
. Isso significa que, quando i
atribuído um novo objeto inteiro, ele não deve afetar os fechamentos criados anteriormente. Infelizmente, inspecionar a adders
matriz em um depurador mostra que sim. Todas as lambda
funções referem-se ao último valor i
, 3
que resulta em adders[1](3)
retorno 6
.
O que me faz pensar sobre o seguinte:
- O que os fechamentos capturam exatamente?
- Qual é a maneira mais elegante de convencer as
lambda
funções a capturar o valor atual dei
uma maneira que não seja afetada quandoi
o valor for alterado?
i
sai do espaço para nome?print i
não funcionaria após o loop. Mas eu testei por mim mesmo e agora entendo o que você quer dizer - funciona. Eu não tinha ideia de que variáveis de loop permaneciam após o corpo do loop em python.if
,with
,try
etc.Respostas:
Sua segunda pergunta foi respondida, mas quanto à sua primeira:
O escopo em Python é
dinâmico elexical. Um fechamento sempre lembrará o nome e o escopo da variável, não o objeto para o qual está apontando. Como todas as funções no seu exemplo são criadas no mesmo escopo e usam o mesmo nome de variável, elas sempre se referem à mesma variável.EDIT: Em relação à sua outra questão de como superar isso, há duas maneiras que vêm à mente:
A maneira mais concisa, mas não estritamente equivalente, é a recomendada por Adrien Plisson . Crie uma lambda com um argumento extra e defina o valor padrão do argumento extra para o objeto que você deseja preservar.
Um pouco mais detalhado, mas menos hacky, seria criar um novo escopo cada vez que você criar o lambda:
O escopo aqui é criado usando uma nova função (um lambda, por questões de concisão), que vincula seu argumento e transmite o valor que você deseja vincular como argumento. No código real, porém, você provavelmente terá uma função comum em vez do lambda para criar o novo escopo:
fonte
set!
. veja aqui o que realmente é o escopo dinâmico: voidspace.org.uk/python/articles/code_blocks.shtml .você pode forçar a captura de uma variável usando um argumento com um valor padrão:
a ideia é declarar um parâmetro (com nome inteligente
i
) e fornecer um valor padrão da variável que você deseja capturar (o valor dei
)fonte
Para completar, outra resposta para sua segunda pergunta: você pode usar parcial no módulo functools .
Com a importação de add from operator como Chris Lutz propôs, o exemplo se torna:
fonte
Considere o seguinte código:
Eu acho que a maioria das pessoas não achará isso confuso. É o comportamento esperado.
Então, por que as pessoas pensam que seria diferente quando isso é feito em um loop? Eu sei que cometi esse erro, mas não sei por quê. É o laço? Ou talvez o lambda?
Afinal, o loop é apenas uma versão mais curta de:
fonte
i
variável está sendo acessada para cada função lambda.Em resposta à sua segunda pergunta, a maneira mais elegante de fazer isso seria usar uma função que usa dois parâmetros em vez de uma matriz:
No entanto, usar o lambda aqui é um pouco bobo. O Python nos fornece o
operator
módulo, que fornece uma interface funcional para os operadores básicos. O lambda acima tem sobrecarga desnecessária apenas para chamar o operador de adição:Entendo que você está brincando, tentando explorar a linguagem, mas não consigo imaginar uma situação em que usaria uma variedade de funções em que a estranheza do escopo do Python atrapalhasse.
Se você quiser, escreva uma classe pequena que use sua sintaxe de indexação de matriz:
fonte
Aqui está um novo exemplo que destaca a estrutura de dados e o conteúdo de um fechamento, para ajudar a esclarecer quando o contexto em anexo é "salvo".
O que há em um fechamento?
Notavelmente, my_str não está no fechamento de f1.
O que há no fechamento da f2?
Observe (nos endereços de memória) que os dois fechamentos contêm os mesmos objetos. Portanto, você pode começar a pensar na função lambda como tendo uma referência ao escopo. No entanto, my_str não está no fechamento de f_1 ou f_2 e i não está no fechamento de f_3 (não mostrado), o que sugere que os próprios objetos de fechamento são objetos distintos.
Os objetos de fechamento são eles mesmos o mesmo objeto?
fonte
int object at [address X]>
me fez pensar que o fechamento está armazenando [endereço X] AKA uma referência. No entanto, [endereço X] será alterado se a variável for reatribuída após a instrução lambda.