Desafio Python
Evitando late binding em closures de loop
Enunciado
Dada uma lista de funções criadas em um loop que deveriam retornar seus índices, corrija o problema de late binding para que cada função retorne o valor correto do índice. Implemente cria_funcoes(n) que retorna uma lista de n funções, onde a função de índice i retorna i.
Requisitos
- A função
cria_funcoes(n)deve retornar uma lista comnfunções. - Cada função deve retornar seu próprio índice, sem late binding.
- Não use
globaldentro das funções retornadas. - A solução deve funcionar para qualquer
n >= 0.
Código inicial
def cria_funcoes(n):
# complete aqui
pass
funcs = cria_funcoes(4)
print([f() for f in funcs])
Saída esperada
[0, 1, 2, 3]
Ver dica
Você pode capturar o valor atual do índice usando um argumento padrão (lambda i=i: i) ou criar uma função fábrica que recebe o índice como parâmetro.
Mostrar solução
def cria_funcoes(n):
"""Retorna uma lista de n funções; a i-ésima retorna i."""
def fabrica(i):
# 'i' é parâmetro local de fabrica, capturado por valor
return lambda: i
return [fabrica(i) for i in range(n)]
funcs = cria_funcoes(4)
print([f() for f in funcs]) # [0, 1, 2, 3]
Passo a passo
cria_funcoes(n)define uma função internafabrica(i)que recebe o índice como parâmetro.- Dentro de
fabrica,return lambda: icria uma closure que captura o parâmetroido escopo defabrica. - Como cada chamada a
fabrica(i)cria um novo escopo com seu próprioi, não há compartilhamento de variável. - A compreensão de lista
[fabrica(i) for i in range(n)]chamafabricapara cadaide 0 a n-1. - Cada função retornada tem sua própria cópia de
i, entãof()devolve o índice correto. - O resultado é
[0, 1, 2, 3]paran = 4.
Por que funciona
O late binding ocorre porque closures capturam variáveis por referência. Se usássemos lambda: i diretamente no loop, todas as lambdas compartilhariam a mesma variável i, que terminaria com o último valor. Ao passar i como parâmetro para fabrica, criamos um novo escopo a cada iteração, e a closure captura uma referência àquele parâmetro específico, que não muda depois.
Erros comuns
- Usar
lambda: idiretamente no loop:
def cria_funcoes(n): return [lambda: i for i in range(n)]
Todas as funções retornam `n-1`, pois compartilham a mesma variável `i`.
- Tentar corrigir com `global` ou `nonlocal`, que não resolve o problema de referência compartilhada.
- Esquecer de chamar `fabrica(i)` e retornar a própria função:
```python
return [fabrica for i in range(n)]
Isso retorna a mesma função fabrica repetida, e f() daria erro por falta de argumento.
Outra forma de resolver
Usar argumento padrão na lambda:
def cria_funcoes(n):
return [lambda i=i: i for i in range(n)]
O valor padrão i=i é avaliado no momento da criação da lambda, capturando o valor atual. É mais conciso, mas pode confundir; a função fábrica é mais explícita.
Saída esperada
[0, 1, 2, 3]