(Embora ilustrado em Python, creio que conhecer Python não seja totalmente necessário para entender o post :) )
Você conhece o conceito de closures (”fecho”, mas acho que ninguém usa o termo em português…)? Basicamente, é quando você pode definir uma função em tempo de execução e esta função faz referência a variáveis de um escopo externo a ela. Por exemplo:
def make_number_printer(n): def number_printer(): print n return number_printer printer = make_number_printer(5) printer()
A função make_number_printer recebe um número e retorna uma função que, quando chamada, imprime esse mesmo número. Não é a função mais útil do universo, mas ilustra bem o conceito de closure. No caso, a função number_printer é uma closure, pois referencia a variável n que está num escopo externo (o da função make_number_printer).
Porém minha intenção aqui não é tanto explicar o que são closures, mas sim ilustrar uma característica que pode acabar confundindo (já perdi um bom tempo caçando um bug causado por isso). O que esse código imprime?
printer_lst = [] for i in xrange(10): def number_printer(): print i printer_lst.append(number_printer) for printer in printer_lst: printer()
Ele executa um loop de 0 a 9, em cada iteração criando uma função number_printer e colocando-a numa lista, e em seguida percorre essa lista chamando todas as funções. Era de se esperar que ele imprimisse os números de 0 a 9, certo?
Errado! Ele imprime o número 9 dez vezes.
O problema é que na maioria das vezes, intuitivamente pensa-se que as closures funcionam “fixando” o valor das variáveis externas que elas referenciam para usá-las num momento posterior. Mas na verdades elas guardam uma referência àquela variável, e se ela mudar de valor, a closure usará o valor alterado. Como a nossa variável i vale 9 no final da criação das closures, esse é o valor que todas vão imprimir. Isso vale mesmo se a variável i deixar de existir após a criação das closures (por exemplo, se o primeiro for estivesse dentro de uma função).
Como resolver isso? Você pode mover a criação da closure para uma outra função, desta forma:
def make_number_printer(n): def number_printer(): print n return number_printer printer_lst = [] for i in xrange(10): printer_lst.append(make_number_printer(i)) for printer in printer_lst: printer()
Assim a closure irá referenciar a variável n, que possui instâncias diferentes para cada closure diferente criada. Outra alternativa é utilizar um “recurso” polêmico do Python, que é o fato de que os valores de parâmetros com valores padrão são avaliados quando a função é definida, e não quando ela é chamada:
printer_lst = [] for i in xrange(10): def number_printer(x=i): print x printer_lst.append(number_printer) for printer in printer_lst: printer()
Ao executar def number_printer(x=i):, o valor de i é avaliado e salvo permanentemente na definição da função; assim, cada vez que a função é definida (a closure é criada) o valor atual de i é “congelado”.
Se alguém se estiver perguntando, “mas eu nunca vou acabar me deparando com isso”, este é um exemplo um pouco mais próximo da realidade (na verdade me bati com ele quando programava um jogo em Flash, que usa ActionScript). Fundamentalmente é o mesmo código acima e apresenta o mesmo problema:
class Button: #Esta é uma classe para simular um botão, suponha #que é parte de uma biblioteca de GUI e por alguma #razão você não pode herdar dela def __init__(self): self.listener = None def set_click_listener(self, fn): self.listener = fn def on_click(self): self.listener() #Cria 10 botões... buttons = [Button() for i in xrange(10)] #E suponha que eles são adicionados na interface #Seta os listeners para o evento de clique. #O número de botões pode mudar no futuro #e todos têm o mesmo código, a única diferença #sendo o índice do botão. Então é melhor fazer isso #dentro de um loop. for i in xrange(10): def on_click(): #Imagine que o código aqui seja mais útil print i buttons[i].set_click_listener(on_click) #Simula um clique em cada botão for j in xrange(10): buttons[j].on_click()