¿Cómo funcionan las funciones anidadas en Python?

def maker(n): def action(x): return x ** n return action f = maker(2) print(f) print(f(3)) print(f(4)) g = maker(3) print(g(3)) print(f(3)) # still remembers 2 

¿Por qué la función anidada recuerda el primer valor 2 a pesar de que maker() ha regresado y salido cuando se llama a action() ?

Puede verlo como todas las variables que se originan en la función principal se reemplazan por su valor real dentro de la función secundaria. De esta manera, no es necesario realizar un seguimiento del scope de la función principal para que la función secundaria se ejecute correctamente.

Véalo como “creando dinámicamente una función”.

 def maker(n): def action(x): return x ** n return action f = maker(2) --> def action(x): --> return x ** 2 

Este es un comportamiento básico en Python, hace lo mismo con múltiples tareas.

 a = 1 b = 2 a, b = b, a 

Python lee esto como

 a, b = 2, 1 

Básicamente inserta los valores antes de hacer nada con ellos.

Básicamente estás creando un cierre .

En informática, un cierre es una función de primera clase con variables libres que están vinculadas en el entorno léxico. Se dice que tal función está “cerrada sobre” sus variables libres.

Lecturas relacionadas: Cierres: ¿por qué son tan útiles?

Un cierre es simplemente una forma más conveniente de dar acceso a una función al estado local.

De http://docs.python.org/reference/compound_stmts.html :

Nota del progtwigdor: las funciones son objetos de primera clase. Una forma ‘def’ ejecutada dentro de una definición de función define una función local que se puede devolver o pasar. Las variables libres utilizadas en la función anidada pueden acceder a las variables locales de la función que contiene la definición. Vea la sección Nombramiento y enlace para más detalles.

Estás definiendo DOS funciones. Cuando usted llama

 f = maker(2) 

estás definiendo una función que devuelve el doble del número, así que

 f(2) --> 4 f(3) --> 6 

A continuación, se define OTRA FUNCIÓN DIFERENTE

 g = maker(3) 

que devuelven tres veces el numero

 g(3) ---> 9 

Pero son DOS funciones diferentes, no es la misma función referenciada, cada una es independiente. Incluso en el ámbito dentro de la función ‘fabricante’ se llama el mismo, no es la misma función, cada vez que llama al maker() está definiendo una función diferente. Es como una variable local, cada vez que llama a la función toma el mismo nombre, pero puede contener valores diferentes. En este caso, la variable ‘acción’ contiene una función (que puede ser diferente)

Eso es lo que se llama ” cierre “. En pocas palabras, para la mayoría, si no para todos los lenguajes de progtwigción que tratan las funciones como un objeto de primera clase , todas las variables que se utilizan dentro de un objeto de función se incluyen (es decir, se recuerdan) siempre que la función esté activa. Es un concepto poderoso si sabes cómo usarlo.

En su ejemplo, la función de action anidada usa la variable n por lo que forma un cierre alrededor de esa variable y la recuerda para las llamadas de función posteriores.

Veamos tres razones comunes para escribir funciones internas.

1. Cierres y funciones de fábrica.

El valor en el ámbito adjunto se recuerda incluso cuando la variable queda fuera del scope o la función en sí misma se elimina del espacio de nombres actual.

 def print_msg(msg): """This is the outer enclosing function""" def printer(): """This is the nested function""" print(msg) return printer # this got changed 

Ahora intentemos llamar a esta función.

 >>> another = print_msg("Hello") >>> another() Hello 

Eso es inusual. La función print_msg() se llamó con la cadena "Hello" y la función devuelta se vinculó al nombre de another . Al llamar a another() , aún se recordaba el mensaje, aunque ya habíamos terminado de ejecutar la función print_msg() . Esta técnica mediante la cual algunos datos ( "Hello" ) se adjuntan al código se llama cierre en Python.

¿Cuándo usar los cierres?

Entonces, ¿para qué sirven los cierres? Los cierres pueden evitar el uso de valores globales y proporcionan algún tipo de ocultación de datos. También puede proporcionar una solución orientada a objetos para el problema. Cuando existen pocos métodos (un método en la mayoría de los casos) para ser implementados en una clase, los cierres pueden proporcionar soluciones alternativas más elegantes. Referencia

2. Encapsulación:

El concepto general de encapsulación es ocultar y proteger el mundo interior del Mundo Exterior. Aquí se puede acceder a las funciones internas solo dentro del mundo externo y están protegidas de todo lo que suceda fuera de la función.

3. Manteniéndolo seco

Tal vez tenga una función gigante que realice el mismo fragmento de código en numerosos lugares. Por ejemplo, puede escribir una función que procese un archivo y desee aceptar un objeto de archivo abierto o un nombre de archivo:

 def process(file_name): def do_stuff(file_process): for line in file_process: print(line) if isinstance(file_name, str): with open(file_name, 'r') as f: do_stuff(f) else: do_stuff(file_name) 

Para más puedes consultar este blog.

Porque en el momento en que creas la función, n era 2 , entonces tu función es:

 def action(x): return x ** 2 

Cuando llama a f (3), x se establece en 3 , por lo que su función devolverá 3 ** 2

La gente respondió correctamente sobre el cierre, es decir: el valor válido para “n” dentro de la acción es el último valor que tenía cada vez que se llamaba “creador”.

Una manera fácil de superar esto es hacer que su Freevar (n) sea una variable dentro de la función de “acción”, que recibe una copia de “n” en el momento en que se ejecuta:

La forma más sencilla de hacer esto es establecer “n” como parámetro cuyo valor predeterminado es “n” en el momento de la creación. Este valor para “n” permanece fijo porque los parámetros predeterminados para una función se almacenan en una tupla que es un atributo de la función en sí misma (action.func_defaults en este caso):

 def maker(n): def action(x, k=n): return x ** k return action 

Uso:

 f = maker(2) # f is action(x, k=2) f(3) # returns 3^2 = 9 f(3,3) # returns 3^3 = 27 

Un uso es devolver una función que mantiene un parámetro.

 def outer_closure(a): # parm = a <- saving a here isn't needed def inner_closure(): #return parm return a # <- a is remembered return inner_closure # set parm to 5 and return address of inner_closure function x5 = outer_closure(5) x5() >5 x6 = outer_closure(6) x6() >6 # x5 inner closure function instance of parm persists x5() >5 

Cuando crea una función con la palabra clave def, está haciendo exactamente eso: está creando un nuevo objeto de función y asignándolo a una variable. En el código que dio, está asignando ese nuevo objeto de función a una variable local llamada acción.

Cuando lo llama por segunda vez, está creando un segundo objeto de función. Entonces, f apunta al primer objeto de la función (cuadrado del valor) y g apunta al segundo objeto de la función (cubo del valor). Cuando Python ve “f (3)”, significa que “ejecuta el objeto de función apuntado para ser variable f y le pasa el valor 3”. F y G y diferentes objetos de función y así devolver diferentes valores.