Se você já escreveu uma função dentro de outra função em Python e percebeu que a função interna "lembrava" das variáveis da função externa mesmo depois dela ter terminado, você encontrou um closure. Closures são um dos conceitos mais elegantes e poderosos da linguagem, fundamentais para entender tópicos avançados como decorators, generators e programação funcional.

Neste guia completo, você vai aprender o que são closures, como elas funcionam nos bastidores do Python, quando e por que usá-las, além de exemplos práticos que vão solidificar seu entendimento. Vamos começar do básico — as regras de escopo do Python — até chegar a aplicações reais que você pode usar hoje no seu código.

O que são as Regras de Escopo do Python?

Antes de entender closures, você precisa dominar como o Python resolve nomes de variáveis. O Python segue a regra LEGB (Local, Enclosing, Global, Built-in) para procurar variáveis. Quando você referencia uma variável, o interpretador busca nesta ordem:

  1. Local — escopo da função atual
  2. Enclosing — escopo da função externa (se houver)
  3. Global — escopo do módulo
  4. Built-in — escopo das funções nativas do Python

Consulte a documentação oficial do Python sobre definição de funções para mais detalhes sobre como o interpretador gerencia esses escopos.

Veja um exemplo simples que ilustra o LEGB:

x = "global"

def externa(): x = "enclosing"

def interna():
    x = "local"
    print(x)

interna()

externa() # Saída: local

Cada escopo tem seu próprio x. Mas o que acontece quando a função interna não define sua própria variável?

Funções Aninhadas e o Escopo Léxico

Python permite definir funções dentro de funções — isso é chamado de funções aninhadas (nested functions). A função interna tem acesso às variáveis do escopo da função externa graças ao escopo léxico (ou estático).

def externa():
    mensagem = "Olá do escopo enclosing!"
def interna():
    print(mensagem)

interna()

externa() # Saída: Olá do escopo enclosing!

A função interna consegue acessar mensagem porque ela está no escopo enclosing da função externa. Isso é escopo léxico em ação: o Python determina o escopo das variáveis em tempo de compilação, não de execução.

Para se aprofundar nas regras de escopo, o tutorial Python Scope & the LEGB Rule do Real Python é uma excelente referência complementar.

O que é um Closure?

Um closure ocorre quando uma função interna "captura" variáveis do escopo da função externa e continua tendo acesso a elas mesmo depois que a função externa já terminou sua execução. Em outras palavras, o closure "lembra" do ambiente onde foi criado.

Para que um closure seja formado, três condições precisam ser satisfeitas:

  1. Existir uma função aninhada (função dentro de função)
  2. A função interna referenciar variáveis do escopo da função externa
  3. A função externa retornar a função interna

Veja o exemplo clássico:

def saudacao(saudacao_inicial):
    def saudar(nome):
        return f"{saudacao_inicial}, {nome}!"
    return saudar

ola = saudacao("Olá") tchau = saudacao("Tchau")

print(ola("Maria")) # Saída: Olá, Maria! print(tchau("João")) # Saída: Tchau, João!

Quando chamamos saudacao("Olá"), a função externa retorna a função saudar. Mesmo depois que saudacao terminou de executar, a função saudar ainda "lembra" que saudacao_inicial é "Olá". Isso é um closure.

Como Closures Funcionam por Baixo dos Panos

O Python expõe o closure através do atributo mágico __closure__. Vamos inspecionar:

print(ola.__closure__)       
print(ola.__closure__[0].cell_contents)  # Saída: Olá
print(tchau.__closure__[0].cell_contents)  # Saída: Tchau

O __closure__ é uma tupla de objetos cell, um para cada variável capturada. Cada cell armazena o valor atual da variável. Isso é como o Python implementa o "ambiente" do closure.

Segundo a especificação da hierarquia de tipos do Python, células de closure (cell objects) são usadas para armazenar variáveis de escopos enclosing.

Se uma função não capturar nenhuma variável externa, __closure__ será None:

def funcao_simples():
    return 42

print(funcao_simples.closure) # Saída: None

A Palavra-chave nonlocal

Por padrão, quando você atribui um valor a uma variável dentro de uma função aninhada, o Python a trata como uma variável local àquela função. Para modificar uma variável do escopo enclosing, você precisa usar a palavra-chave nonlocal.

def contador():
    total = 0
def incrementar():
    nonlocal total
    total += 1
    return total

return incrementar

cont = contador() print(cont()) # 1 print(cont()) # 2 print(cont()) # 3

Sem o nonlocal, você receberia um UnboundLocalError ao tentar modificar total. A PEP 3104 introduziu o nonlocal no Python 3 para resolver exatamente essa limitação.

Importante: nonlocal não funciona para variáveis do escopo global — para isso existe global. O nonlocal só busca nos escopos enclosing (funções externas).

Casos de Uso Práticos de Closures

Closures não são apenas um conceito acadêmico. Eles têm aplicações extremamente práticas no dia a dia do desenvolvimento Python.

1. Fábricas de Funções

O exemplo da saudacao que vimos é uma fábrica de funções. Você pode criar variações de uma mesma lógica:

def multiplicador(fator):
    def multiplicar(numero):
        return numero * fator
    return multiplicar

dobro = multiplicador(2) triplo = multiplicador(3)

print(dobro(5)) # 10 print(triplo(5)) # 15

Esse padrão é amplamente usado em bibliotecas como Flask, onde você pode configurar comportamentos específicos através de closures.

2. Contadores e Acumuladores com Estado

Closures permitem criar funções com estado privado sem usar classes:

def criar_contador():
    contagem = 0
def contar():
    nonlocal contagem
    contagem += 1
    return contagem

return contar

visitas = criar_contador() print(visitas()) # 1 print(visitas()) # 2 print(visitas()) # 3

A variável contagem fica encapsulada dentro do closure, inacessível do lado de fora — é como um "atributo privado" de uma função.

3. Memorização (Caching de Resultados)

Closures são excelentes para implementar memorização, um padrão que armazena resultados de chamadas anteriores para evitar recálculos:

def memoizar(funcao):
    cache = {}
def executar(*args):
    if args not in cache:
        cache[args] = funcao(*args)
        print(f"Calculando... {args}")
    return cache[args]

return executar

@memoizar def fibonacci(n): if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(10)) # Calcula apenas uma vez cada valor

Para uma explicação detalhada sobre como closures se relacionam com decorators, confira nosso guia completo de Decorators em Python.

4. Callbacks Personalizados

Closures são perfeitos para criar callbacks que carregam contexto adicional:

def configurar_logger(nivel):
    def log(mensagem):
        print(f"[{nivel.upper()}] {mensagem}")
    return log

info = configurar_logger("info") erro = configurar_logger("erro")

info("Sistema iniciado") # [INFO] Sistema iniciado erro("Falha na conexão") # [ERRO] Falha na conexão

Closures vs Classes: Qual Usar?

Tanto closures quanto classes permitem criar objetos com estado. A escolha depende do contexto:

Critério Closure Classe
Complexidade Baixa — ideal para uma ou duas variáveis Alta — ideal para múltiplos métodos e atributos
Legibilidade Simples e direto Mais verboso, mas mais explícito
Estado Variáveis encapsuladas no closure Atributos públicos ou privados
Reutilização Limitada (função única) Herança e mixins

Uma regra prática: se você precisa de apenas um método com estado interno, use um closure. Se precisa de múltiplos métodos ou herança, use uma classe. Complemente seus estudos com nosso guia sobre Funções em Python para entender melhor quando usar cada abordagem.

Armadilhas Comuns com Closures

Late Binding (Ligação Tardia)

O problema mais famoso com closures em Python é o late binding. Considere:

def criar_funcoes():
    funcoes = []
    for i in range(5):
        def funcao():
            return i
        funcoes.append(funcao)
    return funcoes

for f in criar_funcoes(): print(f()) # 4 4 4 4 4 (e não 0 1 2 3 4!)

Todas as funções retornam 4 porque o closure captura a referência para i, não seu valor no momento da criação. Quando as funções são executadas (após o loop), i já vale 4.

Solução: Use um argumento padrão para capturar o valor imediatamente:

def criar_funcoes():
    funcoes = []
    for i in range(5):
        def funcao(valor=i):  # Captura i imediatamente
            return valor
        funcoes.append(funcao)
    return funcoes

for f in criar_funcoes(): print(f()) # 0 1 2 3 4

O FAQ oficial do Python explica esse comportamento em detalhes, incluindo por que closures se comportam dessa forma.

Variáveis Mutáveis em Closures

Tenha cuidado ao capturar objetos mutáveis:

def criar_acumulador():
    itens = []
def adicionar(item):
    itens.append(item)
    return itens

return adicionar

acc = criar_acumulador() print(acc(1)) # [1] print(acc(2)) # [1, 2]

Isso funciona, mas você está modificando uma lista que pertence ao closure. Para iniciantes, esse comportamento pode ser surpreendente. Sempre documente claramente quando um closure modifica objetos mutáveis.

Closures e Decorators

Closures são a base dos decorators em Python. Um decorator é essencialmente um closure que recebe uma função e retorna uma versão modificada dela:

def temporizador(funcao):
    import time
def executar(*args, **kwargs):
    inicio = time.time()
    resultado = funcao(*args, **kwargs)
    fim = time.time()
    print(f"{funcao.__name__} executou em {fim - inicio:.4f}s")
    return resultado

return executar

@temporizador def trabalho_pesado(): return sum(range(10**6))

trabalho_pesado()

O decorator temporizador é um closure: ele captura a função original (funcao) e a retorna encapsulada com lógica adicional de temporização.

Considerações de Performance

Closures têm um custo de performance pequeno, mas real. O acesso a variáveis do escopo enclosing é ligeiramente mais lento que variáveis locais porque o Python precisa navegar pela cadeia de escopos. No entanto, para 99% dos casos de uso, essa diferença é irrelevante.

Segundo a documentação oficial sobre o modelo de execução do Python, a resolução de nomes segue regras bem definidas que garantem consistência e previsibilidade. Se você precisa de performance máxima em loops internos, prefira variáveis locais e evite closures em código criticamente executado.

Dicas Finais e Boas Práticas

  • Prefira closures simples: Se um closure precisa de mais de 2-3 variáveis do escopo externo, considere usar uma classe.
  • Documente os closures: Explique quais variáveis estão sendo capturadas e por quê.
  • Evite closures em loops: O late binding pode causar bugs sutis. Use argumentos padrão para capturar valores.
  • Use nonlocal com moderação: Apenas quando precisar reatribuir variáveis do escopo enclosing.
  • Combine closures com functools.partial: Para casos simples de funções parciais, functools.partial pode ser mais claro que um closure.

Conclusão

Closures são uma ferramenta fundamental no cinto de utilidades do desenvolvedor Python. Eles permitem criar funções com estado, fábricas de funções, decorators e callbacks personalizados de forma elegante e concisa. Entender closures profundamente é essencial para dominar tópicos avançados como programação assíncrona, metaprogramação e frameworks web.

Agora que você entende o que são closures, como eles funcionam internamente através do __closure__ e como aplicá-los no dia a dia, está pronto para escrever código Python mais expressivo e eficiente. Pratique criando seus próprios closures — comece com uma fábrica de funções simples e evolua para padrões mais complexos como memorização e decorators.

Referências úteis para continuar seus estudos: