Closures & Captura de Escopo
Uma closure é uma função que se lembra de variáveis do escopo envolvente onde foi definida. Closures habilitam fábricas, decoradores e callbacks - mas a vinculação tardia em loops é um "pé-na-cova" clássico.
Receita
def make_multiplier(factor: int):
def multiply(value: int) -> int:
return value * factor
return multiply
double = make_multiplier(2)
print(double(10)) # 20Quando usar isso:
- Funções de fábrica com comportamento configurado
- Decoradores que envolvem chamáveis
- Manipuladores de eventos que capturam contexto
- Aplicação parcial sem
functools.partial
Exemplo de Trabalho
from typing import Callable
def make_counter(start: int = 0) -> Callable[[], int]:
count = start
def inc() -> int:
nonlocal count
count += 1
return count
return inc
def make_handlers_bad():
return [lambda: i for i in range(3)] # bug de vinculação tardia
def make_handlers_good():
return [lambda i=i: i for i in range(3)]
def tracer(label: str):
def decorate(fn):
def wrapper(*args, **kwargs):
print(f"{label}: entrando")
return fn(*args, **kwargs)
return wrapper
return decorate
@tracer("api")
def ping() -> str:
return "pong"
if __name__ == "__main__":
c = make_counter(10)
print(c(), c())
print([h() for h in make_handlers_bad()])
print([h() for h in make_handlers_good()])
print(ping())O que isso demonstra:
nonlocalmodificacountna função envolvente- Vinculação tardia: lambdas compartilham a variável
i- todas veem o valor final sem o truque do argumento padrão - Argumento padrão
i=icaptura o valor no momento da definição da lambda - Fábrica de decorador
tracerretorna um decorador que fecha sobrelabel
Mergulho Profundo
Como Funciona
- Variáveis livres - Nomes referenciados, mas não definidos na função interna.
- Objetos célula (cell objects) - Variáveis do escopo envolvente armazenadas em células compartilhadas por closures.
- LEGB - A busca interna verifica local, envolvente (enclosing), global, built-in.
- Vinculação tardia (Late binding) - A closure procura a variável quando é chamada, não quando é criada.
nonlocalvsglobal-nonlocalvincula ao escopo envolvente mais próximo;globalao escopo do módulo.
Padrões de Correção para Vinculação Tardia
| Padrão | Código |
|---|---|
| Argumento padrão | lambda i=i: i |
functools.partial | partial(fn, i) |
| Função de fábrica | def make(i): return lambda: i |
Notas do Python
def make():
funcs = []
for i in range(3):
def f(i=i):
return i
funcs.append(f)
return funcsArmadilhas
- Vinculação tardia de lambda em loop - Todos os manipuladores retornam o último índice. Correção: Argumento padrão ou fábrica.
- Fechamento sobre objeto mutável - Lista compartilhada mutada por todas as closures. Correção: Copiar por closure ou capturas imutáveis.
- Mutação global acidental - Função interna que atribui sem
nonlocalcria uma variável local. Correção:nonlocalouglobalexplícito. - Retenção de memória - A closure mantém todo o escopo envolvente vivo. Correção: Minimizar variáveis capturadas.
- Confusão de variável de célula na depuração - Inspecione
__closure__da closure quando estiver confuso.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
functools.partial | Vinculação simples de argumento | Necessita de estado mutável nonlocal |
Classe com __call__ | Functor com estado | Closure simples de uso único |
| lambda | Lógica de uma linha minúscula | Lógica com múltiplas instruções |
Argumento padrão em def | Padrão de fábrica em loop | Já usando partial |
FAQs
O que é uma closure?
Função mais o ambiente capturado de variáveis livres do escopo envolvente.
Por que lambdas em loop estão quebradas?
Todas as closures referenciam a mesma variável i avaliada no momento da chamada - use o padrão i=i.
Quando `nonlocal` é necessário?
Para reatribuir uma variável do escopo envolvente - apenas a leitura precisa da closure sem nonlocal.
closure vs global?
Closure captura um escopo de função envolvente específico; global refere-se a nomes no nível do módulo.
Como inspecionar closures?
Tupla func.__closure__ de células; cell.cell_contents para depuração de valor.
fábrica de decorador?
Função externa recebe configuração, retorna um decorador que fecha sobre a configuração, retorna um wrapper.
padrão de argumento mutável em fábrica de closure?
Mesma armadilha de padrão de argumento mutável que funções normais - crie um novo objeto por chamada no corpo da fábrica.
desempenho de closure?
Pequeno overhead em comparação com uma função simples - negligenciável para callbacks típicos.
closures assíncronas?
async def aninhado fecha sobre as mesmas regras - a vinculação tardia também se aplica a lambdas assíncronas (raro).
tipando closures `Callable`?
Retorne Callable[[int], int] da fábrica para consumidores mypy.
Relacionados
- Funções, Args & Escopo - LEGB
- Escrevendo Decoradores - decoradores baseados em closure
- functools - alternativa
partial - Métodos de Classe vs Instância vs Estáticos - objetos chamáveis
Versões da Pilha: Esta página foi escrita para Python 3.14.0 (estável 3.14, manutenção 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, e uv 0.6+.