Cierres y Captura de Ámbito
Un cierre es una función que recuerda variables del ámbito circundante donde fue definida. Los cierres permiten fábricas, decoradores y callbacks, pero el enlace tardío en bucles es un error clásico.
Receta
def make_multiplier(factor: int):
def multiply(value: int) -> int:
return value * factor
return multiply
double = make_multiplier(2)
print(double(10)) # 20Cuándo usar esto:
- Funciones de fábrica con comportamiento configurado
- Decoradores que envuelven llamables
- Manejadores de eventos que capturan contexto
- Aplicación parcial sin
functools.partial
Ejemplo de Trabajo
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)] # error de enlace tardío
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}: enter")
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())Lo que esto demuestra:
nonlocalmutacounten la función circundante- Enlace tardío: las lambdas comparten la variable
i; todas ven el valor final sin el truco del argumento predeterminado - El argumento predeterminado
i=icaptura el valor en el momento de la definición de la lambda - La fábrica de decoradores
tracerdevuelve un decorador que cierra sobrelabel
Análisis Profundo
Cómo Funciona
- Variables libres - Nombres referenciados pero no definidos en la función interna.
- Objetos celda (Cell objects) - Variables del ámbito circundante almacenadas en celdas compartidas por los cierres.
- LEGB - La búsqueda interna comprueba local, circundante (enclosing), global, incorporado (builtin).
- Enlace tardío (Late binding) - El cierre busca la variable cuando se llama, no cuando se crea.
nonlocalvsglobal-nonlocalenlaza el ámbito circundante más cercano;globalel ámbito del módulo.
Patrones de Corrección para el Enlace Tardío
| Patrón | Código |
|---|---|
| Argumento predeterminado | lambda i=i: i |
functools.partial | partial(fn, i) |
| Función de fábrica | def make(i): return lambda: i |
Notas de Python
def make():
funcs = []
for i in range(3):
def f(i=i):
return i
funcs.append(f)
return funcsErrores Comunes
- Enlace tardío de lambda en bucle - Todos los manejadores devuelven el último índice. Corrección: Argumento predeterminado o fábrica.
- Cerrar sobre objeto mutable - Lista compartida mutada por todos los cierres. Corrección: Copiar por cierre o capturas inmutables.
- Mutación global accidental - La función interna asigna sin
nonlocalcrea una variable local. Corrección:nonlocaloglobalexplícito. - Retención de memoria - El cierre mantiene vivo todo el ámbito circundante. Corrección: Minimizar las variables capturadas.
- Confusión de variables de celda en depuración - Inspeccionar
__closure__del cierre cuando esté perplejo.
Alternativas
| Alternativa | Usar Cuando | No Usar Cuando |
|---|---|---|
functools.partial | Vinculación simple de argumentos | Necesita estado mutable nonlocal |
Clase con __call__ | Functor con estado | Cierre simple de un solo uso |
lambda | Pequeñas líneas únicas | Lógica de múltiples sentencias |
Argumento predeterminado en def | Patrón de fábrica de bucle | Ya se usa partial |
Preguntas Frecuentes
¿Qué es un cierre?
Función más el entorno capturado de variables libres del ámbito circundante.
¿Por qué las lambdas en bucle fallan?
Todos los cierres referencian la misma variable i evaluada en el momento de la llamada; usar el predeterminado i=i.
¿Cuándo se requiere `nonlocal`?
Al reasignar una variable circundante; solo leer necesita un cierre sin nonlocal.
¿Cierre vs `global`?
El cierre captura el ámbito de la función circundante específica; global nombres a nivel de módulo.
¿Inspeccionar cierres?
Tupla func.__closure__ de celdas; cell.cell_contents para depuración de valores.
¿Fábrica de decoradores?
La función externa toma la configuración, devuelve un decorador que cierra sobre la configuración, devuelve un envoltorio.
¿Valor predeterminado mutable en fábrica de cierres?
La misma trampa de valor predeterminado mutable que las funciones normales; objeto nuevo por llamada en el cuerpo de la fábrica.
¿Rendimiento de cierres?
Pequeña sobrecarga frente a una función normal; insignificante para callbacks típicos.
¿Cierres asíncronos?
async def anidado cierra bajo las mismas reglas; el enlace tardío se aplica también a las lambdas asíncronas (raro).
¿Tipar cierres `Callable`?
Devolver Callable[[int], int] de la fábrica para consumidores de mypy.
Relacionado
- Funciones, Argumentos y Ámbito - LEGB
- Escribir Decoradores - decoradores basados en cierres
- functools - alternativa
partial - Métodos de Clase vs Instancia vs Estáticos - objetos llamables
Versiones de Pila: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 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+, y uv 0.6+.