
Passo 1 de 7
Definir e retornar uma função interna
Crie uma fábrica simples de multiplicadores e separe o momento de obter uma função do momento de executá-la.
Trilha de aprendizado · Nível 9 · Tutorial 3
Ao concluir, você será capaz de retornar funções que conservam acesso ao ambiente de criação e mantêm estado local sem depender de variáveis globais.
Definir e retornar uma função interna
Crie uma fábrica simples de multiplicadores e separe o momento de obter uma função do momento de executá-la. 2 min
Entender o que a closure conserva
Veja por que uma função retornada continua encontrando variáveis do ambiente em que foi criada. 3 min
Atualizar estado com nonlocal
Crie um contador que preserva seu estado entre chamadas e use nonlocal para atualizar o vínculo capturado. 4 min
Criar estados independentes
Veja como cada chamada de uma fábrica cria uma closure com seu próprio estado e como isso difere de apenas dar outro nome à mesma função. 3 min
Distinguir mutação de reatribuição
Saiba identificar quando uma closure modifica a lista capturada e quando troca o vínculo para outra lista. 3 min
Compartilhar estado entre funções internas
Crie um par de funções que consulta e atualiza o mesmo estado local de uma única fábrica. 3 min
Aplicar e revisar: um registrador com estado
Integre closures, nonlocal e mutação em uma fábrica de registradores com estado privado e independente. 5 min

Passo 1 de 7
Crie uma fábrica simples de multiplicadores e separe o momento de obter uma função do momento de executá-la.
Uma função pode ser definida dentro do corpo de outra. A função externa pode então devolver a função interna como valor.
Nesta fábrica, criar_multiplicador(3) não faz uma multiplicação ainda: ela produz uma função configurada para multiplicar por 3.
def criar_multiplicador(fator):
def multiplicar(numero):
return numero * fator
return multiplicar
triplicar = criar_multiplicador(3)
Primeiro obtenha a função; depois chame a função obtida.
Use return multiplicar, sem parênteses, para devolver a própria função.
Depois, triplicar(5) chama a função guardada em triplicar e retorna 15. Se você escrevesse return multiplicar(...), estaria chamando a função interna naquele ponto — e precisaria fornecer o argumento numero.
Exemplo
resultado = triplicar(5)
print(resultado) # 15criar_multiplicador(3) cria a função e a associa a triplicar. Só triplicar(5) executa o corpo de multiplicar.
Coloque os acontecimentos na ordem em que ocorrem neste código:
triplicar = criar_multiplicador(3)
resultado = triplicar(5)
Passo 2 de 7
Veja por que uma função retornada continua encontrando variáveis do ambiente em que foi criada.
Quando uma função interna usa um nome definido na função externa e é retornada, ela forma uma closure: uma função que conserva acesso aos vínculos do ambiente envolvente em que foi criada.
Esse escopo envolvente é definido pelo aninhamento dos def, não pelo lugar onde você chama a função retornada.
def criar_saudacao(nome):
def saudar():
return f"Olá, {nome}!"
return saudar
para_ana = criar_saudacao("Ana")
print(para_ana()) # Olá, Ana!
saudar foi criada dentro de criar_saudacao; por isso, encontra o vínculo de nome naquele ambiente.
Em saudar, o nome nome não é parâmetro nem variável local dela. Ele é uma variável livre: seu vínculo pertence ao escopo envolvente.
Ao avaliar um nome, Python procura primeiro no escopo local, depois no envolvente, em seguida no global e, por último, nos nomes embutidos. Neste exemplo, nome é encontrado no escopo envolvente.
Exemplo
mensagem = "global"
para_bia = criar_saudacao("Bia")
print(para_bia()) # Olá, Bia!Mesmo chamada fora de criar_saudacao, a função procura nome no ambiente em que foi definida. O local de chamada não transforma nome em uma variável global.
No código de criar_saudacao, qual é a variável livre da função saudar?
Depois de criar_saudacao("Ana") retornar, essa chamada externa terminou: ela não fica executando em segundo plano. Ainda assim, a closure conserva acesso aos vínculos de que precisa, como nome → "Ana".
A captura não cria automaticamente uma cópia ou uma “fotografia” do valor. A closure conserva acesso ao vínculo do ambiente envolvente. Essa diferença será importante ao trabalhar com estados mutáveis.
Por que para_ana() ainda consegue usar nome depois que criar_saudacao terminou?

Passo 3 de 7
Crie um contador que preserva seu estado entre chamadas e use nonlocal para atualizar o vínculo capturado.
A função externa inicializa total uma única vez. A função proximo é retornada e, quando chamada, deve atualizar esse mesmo vínculo para que o contador não recomece do zero.
O total pertence ao escopo de criar_contador; cada chamada posterior de proximo consulta e atualiza esse estado envolvente.

O corpo da função externa já terminou, mas o vínculo total continua disponível para a closure.
Esta versão expressa a atualização desejada, mas ainda falha ao ser chamada.
def criar_contador():
total = 0
def proximo():
total += 1
return total
return proximo
contar = criar_contador()
print(contar())Atenção
Dentro de proximo, a atribuição em total += 1 faz Python tratar total como um nome local desse corpo inteiro. Portanto, ao avaliar o lado direito de total += 1, ele tenta ler esse local antes de receber um valor e lança UnboundLocalError.
Uma função interna pode apenas ler um vínculo envolvente sem declaração especial. Já uma atribuição ao nome — inclusive += — cria um vínculo local por padrão. Para reatribuir o vínculo da função externa, declare nonlocal total antes de usar ou atribuir esse nome.
Aqui não há atribuição a mensagem; a função interna só consulta o vínculo envolvente.
def criar_saudacao():
mensagem = "Olá"
def mostrar():
return mensagem
return mostrar
print(criar_saudacao()()) # Olánonlocal total seleciona um vínculo de função envolvente já existente — neste caso, o mais próximo que se chama total. Ele não cria uma variável e não se refere a uma variável global.
def criar_contador():
total = 0
def proximo():
nonlocal total
total += 1
return total
return proximo
contar = criar_contador()
print(contar()) # 1
print(contar()) # 2
print(contar()) # 3Para que proximo atualize o total criado por criar_contador, escreva a palavra que completa: _____ total
No seu computador, execute o código do contador funcional da tela anterior. Quais valores foram impressos? Em uma frase, explique o que isso revela sobre o vínculo total entre as chamadas.
Escreva pelo menos 30 caracteres (0/30).

Passo 4 de 7
Veja como cada chamada de uma fábrica cria uma closure com seu próprio estado e como isso difere de apenas dar outro nome à mesma função.
Uma função que cria e retorna outra função pode funcionar como uma fábrica. Cada chamada da fábrica executa novamente a inicialização de total e devolve uma nova closure.
Assim, criar_contador() chamado duas vezes produz dois contadores: cada um conserva seu próprio vínculo para total.
def criar_contador():
total = 0
def contar():
nonlocal total
total += 1
return total
return contar
primeiro = criar_contador()
segundo = criar_contador()
Cada chamada de criar_contador() forma um ambiente separado, mesmo usando o mesmo código da função.
As chamadas podem ser intercaladas. Ainda assim, cada closure atualiza somente o total criado na chamada da fábrica que a produziu.
print(primeiro()) # 1 → estado de primeiro: 1
print(primeiro()) # 2 → estado de primeiro: 2
print(segundo()) # 1 → estado de segundo: 1
print(primeiro()) # 3 → estado de primeiro: 3
print(segundo()) # 2 → estado de segundo: 2
Intercalar chamadas altera a ordem de uso, mas não une os estados.
Compare com esta atribuição:
apelido = primeiroAqui, apelido e primeiro referenciam a mesma função retornada. Não há nova chamada a criar_contador(), portanto não há novo total. Chamar um nome continua o estado observado pelo outro.
Considere:
a = criar_contador()
b = criar_contador()
c = a
print(a())
print(b())
print(c())
print(a())Qual é a saída?
Resumo
Use a origem da referência para identificar o estado.
criar_contador() executa total = 0 em um novo ambiente e retorna uma nova closure.Dica
Para saber se existe um novo estado, pergunte: houve uma nova chamada à fábrica? Se não houve, provavelmente há apenas uma nova referência à closure existente.

Passo 5 de 7
Saiba identificar quando uma closure modifica a lista capturada e quando troca o vínculo para outra lista.
Uma lista criada na função externa pode ser usada como estado de uma closure. Há duas operações diferentes:
itens já se refere, como em itens.append(...) ou itens.clear().itens apontar para outro objeto, como em itens = [].A mutação não atribui ao nome itens; por isso, não precisa de nonlocal. A reatribuição muda o vínculo do nome e precisa de nonlocal.
Compare o que acontece com o vínculo capturado em cada caso.

append modifica a lista existente; = troca o objeto para o qual o nome aponta.
A função interna lê o vínculo itens e chama métodos no objeto ao qual ele aponta.
def criar_lista():
itens = []
def adicionar(item):
itens.append(item) # modifica a lista existente
def limpar():
itens.clear() # modifica a mesma lista
def consultar():
return itens
return adicionar, limpar, consultar
adicionar, limpar, consultar = criar_lista()
adicionar("caderno")
adicionar("caneta")
print(consultar()) # ['caderno', 'caneta']
limpar()
print(consultar()) # []consultar() retorna a própria lista capturada. Portanto, se outra referência receber esse retorno, ela observará mutações posteriores feitas por adicionar ou limpar: a closure não cria uma cópia automática da lista.
Para substituir a lista inteira, declare nonlocal antes de usar ou atribuir itens nesse corpo de função.
def criar_lista():
itens = ["rascunho"]
def reiniciar():
nonlocal itens
itens = [] # agora itens aponta para outra lista
def consultar():
return itens
return reiniciar, consultar
reiniciar, consultar = criar_lista()
reiniciar()
print(consultar()) # []Atenção
itens += ["novo"] também é uma atribuição ao nome itens. Mesmo que listas possam ser alteradas no lugar nessa operação, dentro da função interna você deve usar nonlocal itens. Sem isso, Python trata itens como nome local e a operação falha ao tentar ler esse nome antes da atribuição.
Associe cada operação dentro de uma função interna à regra de escopo correspondente.
Toque em um item e depois no par correspondente.

Passo 6 de 7
Crie um par de funções que consulta e atualiza o mesmo estado local de uma única fábrica.
Uma chamada da fábrica pode criar mais de uma função interna. Se ambas usam a variável saldo definida na função externa, elas acessam o mesmo vínculo capturado.
No exemplo, consultar apenas lê saldo; adicionar troca seu valor por um novo. Por isso, somente adicionar precisa declarar nonlocal saldo.

As duas funções retornadas pela mesma chamada externa apontam para o mesmo vínculo saldo.
Cada chamada de criar_conta retorna uma tupla com duas funções.
def criar_conta(saldo_inicial):
saldo = saldo_inicial
def consultar():
return saldo
def adicionar(valor):
nonlocal saldo
saldo += valor
return consultar, adicionar
consultar_a, adicionar_a = criar_conta(10)
adicionar_a(5)
print(consultar_a()) # 15
Exemplo
Depois de consultar_a, adicionar_a = criar_conta(10), as duas variáveis guardam funções do mesmo par.
adicionar_a(5) reatribui o vínculo capturado: saldo passa de 10 para 15.consultar_a() lê esse mesmo vínculo e retorna 15.A função externa já terminou, mas o vínculo capturado continua disponível para as funções retornadas.
Crie dois pares e intercale as operações.
consultar_a, adicionar_a = criar_conta(10)
consultar_b, adicionar_b = criar_conta(100)
adicionar_a(5)
adicionar_b(20)
adicionar_a(2)
print(consultar_a()) # 17
print(consultar_b()) # 120
No par A, consultar_a e adicionar_a compartilham o saldo iniciado em 10. No par B, as funções compartilham outro saldo, iniciado em 100.
Assim, duas funções retornadas pela mesma chamada compartilham estado. Já uma nova chamada da fábrica cria novos vínculos locais e, portanto, um estado independente.
Dica
consultar não usa nonlocal porque apenas consulta saldo. A declaração é necessária em adicionar porque saldo += valor reatribui o nome saldo.
Considere:
<code>def criar_operacoes():
total = 7
def ler():
return total
def incrementar():
nonlocal total
total += 3
return ler, incrementar
ler, incrementar = criar_operacoes()
incrementar()
resultado = ler()</code>
Qual é o valor de resultado? Explique por que as duas funções observam o mesmo estado e por que apenas incrementar declara nonlocal.
Escreva pelo menos 80 caracteres (0/80).

Passo 7 de 7
Integre closures, nonlocal e mutação em uma fábrica de registradores com estado privado e independente.
Crie uma função criar_registrador() que inicializa dois vínculos locais: total, começando em 0, e historico, começando como uma lista vazia. Ela retorna duas funções: registrar(valor) e consultar().
registrar(valor) soma o valor ao total e adiciona esse valor ao histórico.consultar() devolve o total atual e uma cópia do histórico.As duas funções retornadas pela mesma chamada compartilham os mesmos vínculos capturados. Uma nova chamada da fábrica cria outro conjunto de vínculos, sem usar variáveis globais.
Observe quais operações acessam cada estado.

No mesmo par, as operações compartilham estado. Entre pares criados por chamadas distintas, o estado é separado.
Dica
total += valor reatribui o nome total; por isso, registrar precisa declarar nonlocal total. Já historico.append(valor) modifica a lista existente, sem reatribuir o nome historico; portanto, não precisa de nonlocal.
Em um arquivo Python novo, escreva o código a seguir por conta própria e execute-o com Python 3. Os assert não exibem nada quando passam; se algum falhar, revise o estado esperado antes de comparar sua solução com a referência da próxima tela.
Acrescente estes testes depois da sua implementação.
registrar_a, consultar_a = criar_registrador()
assert consultar_a() == (0, [])
registrar_a(10)
registrar_a(-3)
assert consultar_a() == (7, [10, -3])
# A lista recebida é uma cópia: alterá-la não muda o histórico interno.
total_a, copia_a = consultar_a()
copia_a.append(999)
assert consultar_a() == (7, [10, -3])
# Outra chamada à fábrica cria estado independente.
registrar_b, consultar_b = criar_registrador()
registrar_b(50)
assert consultar_b() == (50, [50])
assert consultar_a() == (7, [10, -3])
print("Verificações concluídas.")Compare a estrutura e o comportamento, não apenas a aparência do código.
def criar_registrador():
total = 0
historico = []
def registrar(valor):
nonlocal total
total += valor
historico.append(valor)
def consultar():
return total, historico.copy()
return registrar, consultar
registrar_a, consultar_a = criar_registrador()
assert consultar_a() == (0, [])
registrar_a(10)
registrar_a(-3)
assert consultar_a() == (7, [10, -3])
total_a, copia_a = consultar_a()
copia_a.append(999)
assert consultar_a() == (7, [10, -3])
registrar_b, consultar_b = criar_registrador()
registrar_b(50)
assert consultar_b() == (50, [50])
assert consultar_a() == (7, [10, -3])
print("Verificações concluídas.")Após executar os assert, relate o que observou. Explique por que total usa nonlocal, por que historico.append(valor) não usa e como os testes demonstram a independência entre os dois registradores.
Escreva pelo menos 100 caracteres (0/100).
Resumo
nonlocal quando uma função interna precisa reatribuir um vínculo de um escopo envolvente, como em total += valor.historico.append(valor), não reatribui o nome e não exige nonlocal.historico.copy() evita expor a lista interna.Parabéns! Você concluiu: Criar closures com estado e nonlocal
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