Lambdas em laços for

Duas vezes num espaço de pouco mais de poucas semana me deparei com duas situações em que o programador era vítima de uma característica pouco conhecida da instrução lambda de python.

Ambas as situações envolviam usar lambda dentro de um laço for para criar funções dinamicamente de acordo com o valor fornecido pelo for naquela iteração. Seria algo mais ou menos assim:

for x in range(10):
algo_assincrono.connect(lambda : foobar(x))

No caso, o programador queria que a funcao foobar fosse chamada para cada x gerado no for, enquanto que na prática a função é sempre chamada com o último valor atribuído a x. Isso acontece porque a instrução lambda de python não é exatamente igual às outras lambdas de outras linguagens.

Em python, lambda é apenas uma mandeira simples de encapsular uma expressão dentro de uma função anônima, sem resolver os nomes das variáveis em tempo de definição. Ou seja, lambdas de python não possuem um escopo (espaço de nomes) próprio, trabalhando no escopo em que foram definidas. E como o laço for também não cria um espaço de nomes próprio, a variável x no caso acima estará com o último valor atribuído a ela até o momento da execução do lambda.

Uma alternativa para esse problema é usar aplicação parcial de funções, disponível como a função partial no módulo functools. Ela trabalha recebendo como primeiro parâmetro a função alvo e em seguida os argumentos que devem ser aplicados parcialmente, retornando uma função que receberá apenas os argumentos restantes. Por exemplo:

>>> def foo(x,y,z):
... return x+y+z
...
>>> foo(3)
Traceback (most recent call last):
File "", line 1, in
TypeError: foo() takes exactly 3 arguments (1 given)
>>> from functools import partial
>>> bar = partial(foo, 3)
>>> bar(4,5)
12
>>> bar(4)
Traceback (most recent call last):
File "", line 1, in
TypeError: foo() takes exactly 3 arguments (2 given)
>>> bar(0,0)
3
>>>

Voltando para o exemplo inicial, utilizando partial, ficaria assim:
for x in range(10):
algo_assincrono.connect(partial(foobar, x))



Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s