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 com n funções.
  • Cada função deve retornar seu próprio índice, sem late binding.
  • Não use global dentro 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

  1. cria_funcoes(n) define uma função interna fabrica(i) que recebe o índice como parâmetro.
  2. Dentro de fabrica, return lambda: i cria uma closure que captura o parâmetro i do escopo de fabrica.
  3. Como cada chamada a fabrica(i) cria um novo escopo com seu próprio i, não há compartilhamento de variável.
  4. A compreensão de lista [fabrica(i) for i in range(n)] chama fabrica para cada i de 0 a n-1.
  5. Cada função retornada tem sua própria cópia de i, então f() devolve o índice correto.
  6. O resultado é [0, 1, 2, 3] para n = 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: i diretamente 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]