
Passo 1 de 7
Prever o resultado de chamadas adiadas
Diferencie a criação de closures de sua execução e preveja o valor consultado quando elas são chamadas após o laço.
Trilha de aprendizado · Nível 9 · Tutorial 4
Ao concluir, você será capaz de diagnosticar capturas inesperadas em funções criadas em laços e escolher quando compartilhar uma variável ou fixar a referência ao valor corrente.
Prever o resultado de chamadas adiadas
Diferencie a criação de closures de sua execução e preveja o valor consultado quando elas são chamadas após o laço. 2 min
Entender por que funções distintas compartilham a variável
Veja por que várias funções criadas no mesmo laço podem consultar o mesmo vínculo — seja com lambda ou com def. 2 min
Fixar a referência com um parâmetro padrão
Use um parâmetro padrão para associar cada função ao valor corrente da iteração, sem consultar depois a variável compartilhada do laço. 2 min
Separar as capturas com uma fábrica
Use uma fábrica de funções para criar um vínculo próprio para cada função produzida no laço. 2 min
Distinguir referência fixada de objeto copiado
Veja o que uma closure conserva ao usar parâmetro padrão ou fábrica: uma referência ao objeto, não uma cópia dele. 2 min
Escolher quando compartilhar e quando fixar
Escolha a estratégia de captura a partir do comportamento esperado e da interface que cada função deve oferecer. 2 min
Corrigir e verificar um conjunto de closures
Aplique o diagnóstico de vinculação tardia em um script completo, corrija as closures e confirme o efeito de mutações e reatribuições. 3 min

Passo 1 de 7
Diferencie a criação de closures de sua execução e preveja o valor consultado quando elas são chamadas após o laço.
Uma função interna pode ser criada em um laço e armazenada em uma lista sem que seu corpo seja executado naquele instante. A execução só acontece quando você chama cada função, por exemplo com funcao().
Em uma closure, o nome vindo da função externa é consultado quando a função interna roda. Portanto, não suponha que a criação automática guardou o valor que o laço tinha naquela iteração.
Leia primeiro sem executar: em que momento numero é consultado?
def criar_consultas():
consultas = []
for numero in range(3):
consultas.append(lambda: numero)
return consultas
consultas = criar_consultas()
print([consulta() for consulta in consultas])Durante o laço, são criadas e adicionadas à lista três funções internas. O corpo de cada lambda ainda não foi executado.
Depois da última iteração, numero vale 2. A função externa retorna a lista; só então a compreensão chama cada consulta(). Em cada chamada, a consulta a numero encontra o valor vigente: 2.
A imagem separa o momento de montar a lista do momento de executar as funções.

As funções são criadas no laço, mas a consulta ao nome acontece somente nas chamadas posteriores.
Qual lista será impressa pelo código apresentado?

Passo 2 de 7
Veja por que várias funções criadas no mesmo laço podem consultar o mesmo vínculo — seja com lambda ou com def.
Dentro de uma chamada de função, o for atualiza o mesmo vínculo da variável do laço a cada iteração. Ele não cria um novo escopo de função para cada volta.
Por isso, funções internas diferentes criadas nesse laço podem apontar para o mesmo vínculo de numero. Quando forem chamadas depois, todas consultarão o valor que esse vínculo tiver naquele momento.
Cada cartão representa uma função distinta. As setas convergem porque todas acessam a mesma variável do laço.

O for atualiza um único vínculo; criar uma função não copia automaticamente o valor atual.
As duas listas são criadas em uma única chamada de montar().
def montar():
com_lambda = []
com_def = []
for numero in range(3):
com_lambda.append(lambda: numero)
def consultar():
return numero
com_def.append(consultar)
return com_lambda, com_def
lambdas, funcoes = montar()
print([f() for f in lambdas]) # [2, 2, 2]
print([f() for f in funcoes]) # [2, 2, 2]lambda e def criam objetos função distintos, mas ambos consultam o mesmo numero do ambiente envolvente. Assim, substituir lambda por def não resolve uma captura compartilhada.
Aqui, cada função é chamada imediatamente e depois do término do laço.
def observar():
funcoes = []
for numero in range(3):
def consultar():
return numero
funcoes.append(consultar)
print("durante:", consultar())
print("depois:", [funcao() for funcao in funcoes])
observar()
# durante: 0
# durante: 1
# durante: 2
# depois: [2, 2, 2]Durante cada iteração, o vínculo compartilhado ainda tem o valor corrente, então a chamada imediata observa 0, depois 1, depois 2. Ao final, o laço deixou esse mesmo vínculo com 2; as chamadas posteriores consultam todas esse valor.
Se três funções internas foram criadas no mesmo laço, elas necessariamente possuem três vínculos independentes para a variável do laço.

Passo 3 de 7
Use um parâmetro padrão para associar cada função ao valor corrente da iteração, sem consultar depois a variável compartilhada do laço.
Em uma função criada no laço, escreva um parâmetro padrão com o valor corrente: valor=i. Esse valor é associado ao objeto função naquela iteração. No corpo, use o parâmetro local valor, e não a variável livre i.
À esquerda, todas as funções consultam o mesmo vínculo i. À direita, cada função possui seu próprio parâmetro padrão valor.

Usar valor=i cria uma associação por função no momento de sua criação.
Dica
A expressão valor=i não muda a regra da variável do laço. Ela apenas faz cada função receber, na criação, um valor padrão próprio para o parâmetro local valor.
Nas duas listas, as chamadas ocorrem depois do término do laço.
def criar_com_def():
funcoes = []
for i in range(3):
def consultar(valor=i):
return valor
funcoes.append(consultar)
return funcoes
def criar_com_lambda():
funcoes = []
for i in range(3):
funcoes.append(lambda valor=i: valor)
return funcoes
print([funcao() for funcao in criar_com_def()]) # [0, 1, 2]
print([funcao() for funcao in criar_com_lambda()]) # [0, 1, 2]Em consultar(valor=i) e lambda valor=i: valor, o corpo retorna valor, que é local à chamada. Como nenhum argumento foi fornecido, Python usa o padrão armazenado em cada função.
O valor fixado é o padrão da chamada, não uma constante inacessível.
funcoes = criar_com_def()
print(funcoes[0]()) # 0: usa o padrão guardado
print(funcoes[0](99)) # 99: argumento explícito substitui o padrão nesta chamada
print(funcoes[0]()) # 0: o padrão da função continua sendo 0Dica
Essa solução adiciona um parâmetro que pode ser informado por quem chama a função. Se a função resultante não puder aceitar esse argumento adicional, há outra abordagem para ver no próximo step.
Complete apenas o conteúdo entre parênteses para que as chamadas sem argumentos retornem [2, 4, 6] após o laço:
<code>funcoes = []
for numero in [2, 4, 6]:
funcoes.append(lambda ( ____ ): fixado)
print([funcao() for funcao in funcoes])</code>
Após a correção anterior, qual é o resultado de funcoes[1](40)?

Passo 4 de 7
Use uma fábrica de funções para criar um vínculo próprio para cada função produzida no laço.
Quando o laço chama uma fábrica, o valor corrente é recebido como argumento. Cada chamada da fábrica cria seu próprio vínculo para esse parâmetro; a função retornada consulta esse vínculo, não a variável reutilizada pelo laço.
Cada seta representa uma chamada distinta da fábrica.

Três chamadas da fábrica criam três ambientes: cada função retornada acessa um vínculo diferente.
A função retornada não recebe argumentos: sua interface permanece sem parâmetros.
def criar_consulta(valor):
def consultar():
return valor
return consultar
consultas = []
for numero in [2, 4, 6]:
consultas.append(criar_consulta(numero))
print([consulta() for consulta in consultas]) # [2, 4, 6]criar_consulta(numero) é executada em cada iteração. Assim, as três funções consultar foram criadas em chamadas externas diferentes. Em contraste, definir várias funções diretamente no mesmo corpo externo faria todas consultarem o mesmo vínculo do laço.
Dica
Diferentemente de uma solução com parâmetro padrão exposto, consultar continua sendo chamada simplesmente como consulta(). O parâmetro valor existe apenas na fábrica.
Considere o código abaixo, que deve produzir três funções sem argumentos, uma para cada cor:
mensagens = []
for cor in ["azul", "verde", "vermelho"]:
# complete aquiEscreva uma fábrica curta e a linha que deve entrar no laço. Em seguida, explique por que as funções retornadas não consultam o mesmo vínculo de cor.
Escreva pelo menos 80 caracteres (0/80).

Passo 5 de 7
Veja o que uma closure conserva ao usar parâmetro padrão ou fábrica: uma referência ao objeto, não uma cópia dele.
Ao escrever lambda registro=registro: registro, o parâmetro padrão guarda a referência atual ao objeto. Ele não cria uma nova lista nem um novo dicionário.
Por isso, se esse mesmo objeto for alterado depois, a função verá a alteração.

Mutação altera o objeto já referenciado; reatribuição troca apenas a associação da variável externa.
Execute e observe os dois resultados.
def criar_consulta(registro):
return lambda registro=registro: registro
pedido = {"status": "aberto"}
consultar = criar_consulta(pedido)
pedido["status"] = "enviado" # muta o mesmo dicionário
print(consultar()) # {'status': 'enviado'}
pedido = {"status": "cancelado"} # associa o nome a outro dicionário
print(consultar()) # {'status': 'enviado'}Atenção
pedido["status"] = "enviado" modifica o dicionário original. Já pedido = {...} faz o nome pedido apontar para outro dicionário. A closure mantém a referência que já havia fixado.
Considere:
def criar_leitor(itens):
return lambda itens=itens: itens
nomes = ["Ana"]
leitor = criar_leitor(nomes)
nomes.append("Beto")A chamada leitor() retorna ["Ana", "Beto"].
Uma fábrica cria um vínculo próprio em cada chamada, mas os argumentos recebidos ainda podem ser referências ao mesmo objeto. Portanto, vínculos independentes não implicam objetos independentes.
As duas chamadas da fábrica recebem a mesma lista.
def criar_leitor(valor):
def ler():
return valor
return ler
etiquetas = ["novo"]
ler_a = criar_leitor(etiquetas)
ler_b = criar_leitor(etiquetas)
etiquetas.append("urgente")
print(ler_a()) # ['novo', 'urgente']
print(ler_b()) # ['novo', 'urgente']No código anterior, ler_a e ler_b têm vínculos capturados independentes, mas ambos observam a alteração em etiquetas.

Passo 6 de 7
Escolha a estratégia de captura a partir do comportamento esperado e da interface que cada função deve oferecer.
A vinculação tardia não é necessariamente um defeito. Mantenha o vínculo compartilhado quando todas as funções devem consultar o valor mais atual da variável externa no momento da chamada.
Fixe a referência quando cada função deve manter a associação que recebeu ao ser criada. Se a exigência for que cada função tenha também um objeto mutável independente, fixar a referência não basta: será preciso criar uma cópia adequada do objeto.
Compare o que cada estratégia preserva.

Compartilhar um vínculo, conservar referências distintas e ter objetos independentes são requisitos diferentes.
Exemplo
Use parâmetro padrão quando deixar um parâmetro opcional explícito for aceitável e claro:
leitores = [lambda item=item: item for item in itens]Cada leitor pode ser chamado sem argumento, mas leitores[0](outro_item) substitui o padrão naquela chamada.
Prefira uma fábrica quando a função resultante deve expor somente a interface desejada, sem esse parâmetro opcional:
def criar_leitor(item):
def ler():
return item
return ler
leitores = [criar_leitor(item) for item in itens]Nos dois casos, a referência corrente é conservada. Nenhum dos dois cria uma cópia automática de item.
Dica
Relacione cada requisito à estratégia mais adequada.
Toque em um item e depois no par correspondente.

Passo 7 de 7
Aplique o diagnóstico de vinculação tardia em um script completo, corrija as closures e confirme o efeito de mutações e reatribuições.
Uma função de consulta deve ser criada para cada registro e chamada apenas depois que criar_consultas terminar. Cada consulta precisa conservar a referência ao registro correspondente à sua criação.
No código inicial, todas as lambdas consultam a mesma variável registro do laço quando são chamadas. Portanto, elas acabam retornando o último registro.
Compare as setas antes de alterar o código.

À esquerda, a consulta tardia usa um vínculo compartilhado. À direita, cada função conserva a referência do momento em que foi criada.
No seu computador, crie um arquivo chamado consultas.py, copie o script abaixo e execute python consultas.py.
Primeiro, rode sem alterar nada: o primeiro assert deve falhar. Em seguida, corrija somente a linha marcada com # CORRIJA AQUI, usando uma das estratégias estudadas. Execute de novo até que não haja saída nem erro — isso indica que todos os asserts passaram.
Script inicial com uma captura tardia intencional.
def criar_consultas(registros):
consultas = []
for registro in registros:
# CORRIJA AQUI: cada consulta deve conservar este registro.
consultas.append(lambda: registro)
return consultas
registros = [
{"id": "A1", "status": "novo"},
{"id": "B2", "status": "em análise"},
{"id": "C3", "status": "concluído"},
]
consultas = criar_consultas(registros)
# Cada função deve retornar o objeto que correspondia à sua iteração.
assert consultas[0]() is registros[0]
assert consultas[1]() is registros[1]
assert consultas[2]() is registros[2]
# A mutação do mesmo objeto deve continuar visível na consulta que o referencia.
registros[0]["status"] = "revisado"
assert consultas[0]()["status"] == "revisado"
# Trocar o objeto na lista não redireciona a referência antes conservada.
registros[0] = {"id": "A1", "status": "substituído"}
assert consultas[0]()["status"] == "revisado"
assert consultas[0]() is not registros[0]
print("Verificações concluídas.")Dica
Para usar parâmetro padrão, substitua a linha marcada por:
consultas.append(lambda registro=registro: registro)
O parâmetro padrão é avaliado ao criar cada lambda. A função passa a usar seu parâmetro local registro, e não consulta a variável livre do laço. Você também pode resolver com uma fábrica de funções, se preferir manter a função retornada sem esse parâmetro opcional.
Os três primeiros asserts verificam identidades: cada consulta retorna exatamente o objeto da respectiva posição original.
Depois, a alteração de status é uma mutação no mesmo dicionário; por isso ela aparece na primeira consulta. Já a atribuição de um novo dicionário a registros[0] troca o objeto guardado na lista, mas não altera a referência que a closure já conservava.
Qual estratégia você usou para corrigir a captura? Explique o que ocorreu após a mutação do primeiro registro e após a substituição de registros[0] por outro dicionário.
Escreva pelo menos 120 caracteres (0/120).
Resumo
Parabéns! Você concluiu: Controlar a vinculação tardia em closures
Milhares de cursos online em vídeo, ebooks e áudiobooks.
Para testar seus conhecimentos no decorrer dos cursos online
Gerado diretamente na galeria de fotos do seu celular e enviado ao seu e-mail
Baixe nosso aplicativo pelo QR Code ou pelos links abaixo:.
+ de 10 milhões
de alunos
Certificado grátis e
válido em todo o Brasil
60 mil exercícios
gratuitos
4,8/5 classificação
nas lojas de apps
Cursos gratuitos em
vídeo, ebooks e audiobooks