W3docs

Closures em Python

Aprenda como funcionam os closures em Python: funções aninhadas, variáveis capturadas, nonlocal, armadilhas comuns e casos de uso reais com exemplos executáveis.

Um closure é uma função aninhada que lembra as variáveis do escopo envolvente no qual foi definida, mesmo após a função externa ter retornado. Closures permitem que você anexe estado privado a uma função sem usar uma classe — tornando-os uma das ferramentas mais elegantes do Python para construir callbacks, fábricas e auxiliares com estado.

Esta página cobre como os closures funcionam, as três condições que eles exigem, armadilhas comuns e casos de uso práticos.

O que é um Closure?

Quando Python executa uma função, ele cria um escopo local que desaparece assim que a função retorna. Normalmente, qualquer variável definida ali desaparece. Um closure é a exceção: se uma função interna referencia uma variável de uma função externa, Python mantém essa variável viva em um objeto cell especial, e a função interna carrega uma referência a essas cells para onde quer que vá.

O closure mais simples é uma fábrica de funções — uma função que constrói e retorna outra função:

def make_multiplier(factor):
    def multiply(n):
        return n * factor   # 'factor' is captured from the enclosing scope
    return multiply

double = make_multiplier(2)
triple = make_multiplier(3)

print(double(5))   # 10
print(triple(5))   # 15
print(double(10))  # 20

Cada chamada a make_multiplier cria um closure novo com sua própria cópia independente de factor. double e triple são completamente independentes, mesmo tendo sido criados pela mesma função.

Três Condições para um Closure

Uma função é um closure quando todas estas três condições são verdadeiras:

  1. Existe uma função aninhada — uma função definida dentro de outra função.
  2. A função aninhada referencia uma variável do escopo envolvente — essa variável é chamada de variável livre.
  3. A função envolvente retorna a função aninhada (ou a passa para outro lugar).
def outer():
    message = 'Hello from outer'  # free variable
    def inner():
        print(message)            # inner references it
    return inner                  # outer returns inner

greet = outer()
greet()   # Hello from outer

Após outer() retornar, seu frame local desaparece — mas message sobrevive dentro de greet.__closure__.

Inspecionando um Closure

Python expõe as cells de closure por meio do atributo __closure__:

def make_adder(n):
    def add(x):
        return x + n
    return add

add5 = make_adder(5)
print(add5(3))                            # 8
print(add5.__closure__)                   # (<cell at 0x...>,)
print(add5.__closure__[0].cell_contents)  # 5

__closure__ é uma tupla de objetos cell — um por variável capturada. Se uma função não é um closure, __closure__ é None.

Modificando Variáveis Capturadas com nonlocal

Por padrão, você pode ler uma variável capturada, mas não pode religar ela. Tentar atribuir a ela cria uma nova variável local em vez disso, o que geralmente não é o que você quer. Use a palavra-chave nonlocal para dizer ao Python que você se refere à variável do escopo envolvente:

def make_counter(start=0):
    count = start
    def increment(step=1):
        nonlocal count      # rebind the enclosing 'count', not a new local
        count += step
        return count
    return increment

counter = make_counter()
print(counter())    # 1
print(counter())    # 2
print(counter(5))   # 7

counter2 = make_counter(10)
print(counter2())   # 11
print(counter())    # 8  — counter is unaffected

Cada chamada a make_counter produz uma cell count independente. counter e counter2 não compartilham estado.

Para uma análise mais aprofundada de como Python decide a qual escopo uma variável pertence, veja Python Scope.

Armadilha Comum: Closures em Loops

Um erro clássico é criar closures dentro de um loop e esperar que cada um capture o valor atual da variável do loop:

# Wrong — all functions capture the same 'i' cell
funcs = []
for i in range(3):
    funcs.append(lambda: i)

print([f() for f in funcs])  # [2, 2, 2]  — not [0, 1, 2]

Todos os três lambdas compartilham uma cell que guarda a variável do loop i. Quando são chamados, i já chegou ao seu valor final de 2.

Correção 1: Argumento padrão (captura por valor)

funcs = []
for i in range(3):
    funcs.append(lambda i=i: i)   # default arg is evaluated immediately

print([f() for f in funcs])  # [0, 1, 2]

Correção 2: Função fábrica

def make_func(i):
    def f():
        return i
    return f

funcs = [make_func(i) for i in range(3)]
print([f() for f in funcs])  # [0, 1, 2]

A função fábrica cria um escopo novo — e, portanto, uma cell nova — para cada iteração. Esta é a abordagem mais explícita e legível.

Casos de Uso Práticos

Aplicação Parcial

Closures são uma alternativa leve a functools.partial quando você precisa de uma versão pré-configurada de uma função:

def make_power(exponent):
    def power(base):
        return base ** exponent
    return power

square = make_power(2)
cube   = make_power(3)

print(square(4))  # 16
print(cube(3))    # 27

Memoização Simples

Um closure pode guardar um dicionário de cache que persiste entre as chamadas:

def make_memoized(func):
    cache = {}
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrapper

@make_memoized
def slow_square(n):
    return n * n

print(slow_square(4))   # 16
print(slow_square(4))   # 16 (served from cache)
print(slow_square(7))   # 49

Este padrão é exatamente como os Decorators do Python funcionam internamente — um decorator é apenas um closure que envolve outra função.

Configuração de Callbacks

Closures são úteis para construir callbacks que precisam de um pouco de contexto incorporado:

def make_logger(prefix):
    def log(message):
        print(f'[{prefix}] {message}')
    return log

info  = make_logger('INFO')
error = make_logger('ERROR')

info('Server started')    # [INFO] Server started
error('Disk full')        # [ERROR] Disk full

Closures vs. Classes

Um closure e uma classe com um único método frequentemente resolvem o mesmo problema. Escolha com base na complexidade:

SituaçãoPrefira
Um estado, um comportamentoClosure
Múltiplos métodos ou atributos públicosClasse
Precisa ser serializado (ex.: pickle)Classe
Passando um callback para outra funçãoClosure
# Class approach
class Counter:
    def __init__(self, start=0):
        self.count = start
    def increment(self, step=1):
        self.count += step
        return self.count

# Closure approach
def make_counter(start=0):
    count = start
    def increment(step=1):
        nonlocal count
        count += step
        return count
    return increment

Ambos produzem comportamento idêntico. O closure é mais curto; a classe é mais descobrível e extensível.

Conclusão

Closures permitem que uma função aninhada carregue seu próprio estado privado ao lembrar as variáveis do escopo onde foi definida. Os pontos principais são:

  • Um closure requer uma função aninhada, uma variável livre e a função interna sendo retornada ou passada adiante.
  • Use nonlocal quando precisar religar (não apenas ler) uma variável capturada.
  • Evite a armadilha da variável de loop: use uma função fábrica ou um argumento padrão para capturar o valor em cada iteração.
  • Closures são a base dos decorators e estão intimamente relacionados às regras de escopo do Python.

Prática

Prática
Which keyword allows an inner function to rebind a variable from its enclosing scope?
Which keyword allows an inner function to rebind a variable from its enclosing scope?
Prática
What does the __closure__ attribute return when a function is NOT a closure?
What does the __closure__ attribute return when a function is NOT a closure?
Was this page helpful?