ProgramaciónDesarrollador de Python

¿Cómo funcionan los closures en Python? ¿Cuáles son las características del acceso a las variables de la función externa y cómo evitar trampas relacionadas con la mutabilidad de las variables?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

Los closures son un mecanismo poderoso en Python que permite crear funciones con datos "recordados" del contexto circundante.

Historia de la pregunta

En lenguajes funcionales y en Python, las funciones son primeros ciudadanos. Una función puede devolver una nueva función que "cierra" las variables de su ámbito (externo).

Problema

Los desarrolladores a menudo se confunden sobre cómo exactamente viven las variables cerradas dentro de un closure, cómo se leen o escriben, y cómo trabajar con ellas de manera segura (especialmente con objetos mutables).

Solución

Si una función interna utiliza variables de la función externa, automáticamente se "recuerdan" — incluso si la función externa ya ha terminado de ejecutarse. Para leer una variable todo es evidente, pero si es necesario cambiar el valor — se requiere usar la palabra clave nonlocal. Al trabajar con objetos mutables (listas, diccionarios) esto es una zona de riesgo especial.

Ejemplo:

def outer(): count = 0 def inner(): nonlocal count count += 1 return count return inner counter = outer() print(counter()) # 1 print(counter()) # 2

Características clave:

  • La función anidada recuerda los valores de las variables que estaban en el ámbito en el momento de su creación.
  • Para modificar las variables cerradas, usa nonlocal (de lo contrario, la operación crea una variable local).
  • Las referencias a los objetos en un closure se mantienen incluso después de salir de la función externa, lo que permite implementar fábricas de funciones, contadores léxicos y mucho más.

Preguntas trampa.

¿Se puede cambiar el valor de una variable cerrada dentro de una función sin nonlocal?

No. Si intentas asignar un nuevo valor sin indicar nonlocal, Python lo interpreta como la creación de una nueva variable local, y el antiguo valor no saldrá al exterior.

Ejemplo:

def make_counter(): count = 0 def inner(): count += 1 # Error UnboundLocalError! return count return inner

¿Se pueden pasar argumentos al ámbito exterior a través de un closure?

Sí, un closure "recordará" cualquier variable accesible en el ámbito externo, incluidos atributos de clases, variables globales, etc. Pero modificar estas variables requiere esfuerzos especiales (por ejemplo, el uso de nonlocal o global).

¿Qué sucede con los objetos mutables dentro de un closure?

Si una variable cerrada es una referencia a un objeto mutable, por ejemplo, una lista, puedes modificar su contenido sin nonlocal, pero si intentas reasignar la variable, necesitarás nonlocal.

Ejemplo:

def make_appender(): result = [] def append(x): result.append(x) # ¡Es posible! return result return append f = make_appender() print(f(1)) # [1] print(f(2)) # [1, 2]

Errores comunes y anti-patrones

  • Intentar reasignar una variable en un closure sin nonlocal.
  • Usar un closure para almacenar estado mutable, sin darse cuenta de la posible fuga de memoria.
  • Código difícil de leer debido a un número excesivo de variables cerradas.

Ejemplo de la vida real

Caso negativo

Un desarrollador escribe un closure, modifica una variable sin nonlocal — se produce un UnboundLocalError.

Ventajas:

  • Prototipado rápido de contadores, fábricas.

Desventajas:

  • Comportamiento impredecible, errores debido a problemas con nonlocal.

Caso positivo

Uso explícito de nonlocal para un estado controlado en un closure.

Ventajas:

  • Estado claramente controlado, fácil de implementar contadores y fábricas de funciones.

Desventajas:

  • Más difícil de entender y depurar largas cadenas de closures.