¿Cómo obtener la función actual en una variable?

¿Cómo puedo obtener una variable que contenga la función que se está ejecutando actualmente en Python? No quiero el nombre de la función. Sé que puedo usar inspect.stack para obtener el nombre de la función actual. Quiero el objeto real llamable. ¿Se puede hacer esto sin usar inspect.stack para recuperar el nombre de la función y luego eval el nombre para obtener el objeto que se puede llamar?

Edit: Tengo una razón para hacer esto, pero ni siquiera es una buena remotamente. Estoy usando plac para analizar los argumentos de la línea de comandos. Se usa haciendo plac.call(main) , que genera un objeto ArgumentParser a partir de la firma de función de “main”. Dentro de “main”, si hay un problema con los argumentos, quiero salir con un mensaje de error que incluye el texto de ayuda del objeto ArgumentParser, lo que significa que necesito acceder directamente a este objeto llamando a plac.parser_from(main).print_help() . Sería bueno poder decir en su lugar: plac.parser_from(get_current_function()).print_help() , de modo que no confío en que la función se llame “main”. En este momento, mi implementación de “get_current_function” sería:

 import inspect def get_current_function(): return eval(inspect.stack()[1][3]) 

Pero esta implementación se basa en la función que tiene un nombre, que supongo que no es demasiado onerosa. Nunca voy a hacer plac.call(lambda ...) .

A largo plazo, podría ser más útil pedirle al autor de plac que implemente un método print_help para imprimir el texto de ayuda de la función que se llamó más recientemente usando plac, o algo similar.

El marco de la stack nos dice en qué objeto de código estamos. Si podemos encontrar un objeto de función que se refiera a ese objeto de código en su atributo __code__ , hemos encontrado la función.

Afortunadamente, podemos preguntar al recolector de basura qué objetos contienen una referencia a nuestro objeto de código y analizarlos, en lugar de tener que atravesar cada objeto activo en el mundo de Python. Por lo general, solo hay un puñado de referencias a un objeto de código.

Ahora, las funciones pueden compartir objetos de código, y lo hacen en el caso de que devuelva una función desde una función, es decir, un cierre. Cuando hay más de una función que usa un objeto de código dado, no podemos decir qué función es, por lo que devolvemos None .

 import inspect, gc def giveupthefunc(): frame = inspect.currentframe(1) code = frame.f_code globs = frame.f_globals functype = type(lambda: 0) funcs = [] for func in gc.get_referrers(code): if type(func) is functype: if getattr(func, "__code__", None) is code: if getattr(func, "__globals__", None) is globs: funcs.append(func) if len(funcs) > 1: return None return funcs[0] if funcs else None 

Algunos casos de prueba:

 def foo(): return giveupthefunc() zed = lambda: giveupthefunc() bar, foo = foo, None print bar() print zed() 

No estoy seguro de las características de rendimiento de esto, pero creo que debería estar bien para su caso de uso.

Esto es lo que pediste, lo más cerca que puedo venir. Probado en las versiones de python 2.4, 2.6, 3.0.

 #!/usr/bin/python def getfunc(): from inspect import currentframe, getframeinfo caller = currentframe().f_back func_name = getframeinfo(caller)[2] caller = caller.f_back from pprint import pprint func = caller.f_locals.get( func_name, caller.f_globals.get( func_name ) ) return func def main(): def inner1(): def inner2(): print("Current function is %s" % getfunc()) print("Current function is %s" % getfunc()) inner2() print("Current function is %s" % getfunc()) inner1() #entry point: parse arguments and call main() if __name__ == "__main__": main() 

Salida:

 Current function is  Current function is  Current function is  

Recientemente pasé mucho tiempo tratando de hacer algo como esto y terminé alejándome de eso. Hay muchos casos de esquina.

Si solo desea el nivel más bajo de la stack de llamadas, puede hacer referencia al nombre que se usa en la statement de def . Esto estará vinculado a la función que desee a través del cierre léxico.

Por ejemplo:

 def recursive(*args, **kwargs): me = recursive 

Ahora me referiré a la función en cuestión, independientemente del scope desde el que se llame la función, siempre que no se redefina en el scope donde se produce la definición. ¿Hay alguna razón por la que esto no funcionará?

Para obtener una función que se está ejecutando más arriba en la stack de llamadas, no se me ocurre nada que pueda hacerse de manera confiable.

Aquí hay otra posibilidad: un decorador que pasa implícitamente una referencia a la función llamada como el primer argumento (similar a los métodos de instancia unida en self ). Debe decorar cada función que desee que reciba dicha referencia, pero “explícito es mejor que implícito”, como dicen.

Por supuesto, tiene todas las desventajas de los decoradores: otra función llama a degradar el rendimiento, y la firma de la función envuelta ya no es visible.

 import functools def gottahavethatfunc(func): @functools.wraps(func) def wrapper(*args, **kwargs): return func(func, *args, **kwargs) return wrapper 

El caso de prueba ilustra que la función decorada todavía obtiene la referencia a sí misma incluso si cambia el nombre al que está vinculada la función. Esto se debe a que solo está cambiando el enlace de la función de envoltura. También ilustra su uso con un lambda.

 @gottahavethatfunc def quux(me): return me zoom = gottahavethatfunc(lambda me: me) baz, quux = quux, None print baz() print zoom() 

Cuando se utiliza este decorador con un método de instancia o clase, el método debe aceptar la referencia de función como primer argumento y el self tradicional como segundo.

 class Demo(object): @gottahavethatfunc def method(me, self): return me print Demo().method() 

El decorador se basa en un cierre para mantener la referencia a la función envuelta en el envoltorio. Crear el cierre directamente puede ser más limpio y no tendrá la sobrecarga de la llamada a la función adicional:

 def my_func(): def my_func(): return my_func return my_func my_func = my_func() 

Dentro de la función interna, el nombre my_func siempre se refiere a esa función; su valor no se basa en un nombre global que se puede cambiar. Luego simplemente “elevamos” esa función al espacio de nombres global, reemplazando la referencia a la función externa. Trabaja en una clase también:

 class K(object): def my_method(): def my_method(self): return my_method return my_method my_method = my_method() 

Simplemente defino al comienzo de cada función una “palabra clave” que es solo una referencia al nombre real de la función. Solo hago esto para cualquier función, si la necesita o no:

 def test(): this=test if not hasattr(this,'cnt'): this.cnt=0 else: this.cnt+=1 print this.cnt 

La stack de llamadas no guarda una referencia a la función en sí misma, aunque el marco en ejecución como referencia al objeto de código que es el código asociado a una función dada.

(Las funciones son objetos con código y cierta información sobre su entorno, como cierres, nombres, diccionario global, cadena de documentos, parámetros predeterminados, etc.).

Por lo tanto, si está ejecutando una función normal, es mejor usar su propio nombre en el diccionario global para llamarse a sí mismo, como se ha señalado.

Si está ejecutando algún código dynamic, o lambda, en el que no puede usar el nombre de la función, la única solución es reconstruir otro objeto de función que reutiliza el objeto de código actualmente en ejecución y llamar a esa nueva función en su lugar.

Perderá un par de cosas, como los argumentos predeterminados, y puede ser difícil hacerlo funcionar con cierres (aunque se puede hacer).

He escrito una publicación en el blog sobre cómo hacer exactamente eso: llamar a funciones anónimas desde dentro de ellos mismos, espero que el código que hay allí pueda ayudarte:

http://metapython.blogspot.com/2010/11/recursive-lambda-functions.html

En una nota al margen: evite el uso o inspect.stack: es demasiado lento, ya que reconstruye mucha información cada vez que se le llama. Prefiero usar inspect.currentframe para tratar con marcos de código en su lugar.

Esto puede parecer complicado, pero el código en sí es muy corto; lo estoy pegando a continuación. La publicación anterior contiene más información sobre cómo funciona esto.

 from inspect import currentframe from types import FunctionType lambda_cache = {} def myself (*args, **kw): caller_frame = currentframe(1) code = caller_frame.f_code if not code in lambda_cache: lambda_cache[code] = FunctionType(code, caller_frame.f_globals) return lambda_cache[code](*args, **kw) if __name__ == "__main__": print "Factorial of 5", (lambda n: n * myself(n - 1) if n > 1 else 1)(5) 

Si realmente necesita la función original en sí misma, la función “yo mismo” anterior se puede hacer para buscar en algunos ámbitos (como el diccionario global de la función de llamada) un objeto de función cuyo objeto de código coincidiría con el recuperado del marco, en lugar de creando una nueva función.

sys._getframe (0) .f_code devuelve exactamente lo que necesita: el objeto de código que se está ejecutando. Al tener un objeto de código, puede recuperar un nombre con codeobject .co_name

OK después de leer la pregunta y los comentarios nuevamente, creo que este es un caso de prueba decente:

 def foo(n): """ print numbers from 0 to n """ if n: foo(n-1) print n g = foo # assign name 'g' to function object foo = None # clobber name 'foo' which refers to function object g(10) # dies with TypeError because function object tries to call NoneType 

Intenté resolverlo utilizando un decorador para golpear temporalmente el espacio de nombres global y reasignar el objeto de función al nombre original de la función:

 def selfbind(f): """ Ensures that f's original function name is always defined as f when f is executed """ oname = f.__name__ def g(*args, **kwargs): # Clobber global namespace had_key = None if globals().has_key(oname): had_key = True key = globals()[oname] globals()[oname] = g # Run function in modified environment result = f(*args, **kwargs) # Restore global namespace if had_key: globals()[oname] = key else: del globals()[oname] return result return g @selfbind def foo(n): if n: foo(n-1) print n g = foo # assign name 'g' to function object foo = 2 # calling 'foo' now fails since foo is an int g(10) # print from 0..10, even though foo is now an int print foo # prints 2 (the new value of Foo) 

Estoy seguro de que no he pensado en todos los casos de uso. El mayor problema que veo es que el objeto de función cambia intencionalmente a lo que apunta su propio nombre (una operación que el decorador sobrescribiría), pero debería estar bien siempre que la función recursiva no redefina su propio nombre en el medio. de recursiva.

Todavía no estoy seguro de si alguna vez necesitaría hacer esto, pero pensar en eso fue interesante.

Aquí responde una variación (Python 3.5.1) de get_referrers (), que trata de distinguir entre los cierres que utilizan el mismo objeto de código:

 import functools import gc import inspect def get_func(): frame = inspect.currentframe().f_back code = frame.f_code return [ referer for referer in gc.get_referrers(code) if getattr(referer, "__code__", None) is code and set(inspect.getclosurevars(referer).nonlocals.items()) <= set(frame.f_locals.items())][0] def f1(x): def f2(y): print(get_func()) return x + y return f2 f_var1 = f1(1) f_var1(3) # .f2 at 0x0000017235CB2C80> # 4 f_var2 = f1(2) f_var2(3) # .f2 at 0x0000017235CB2BF8> # 5 

 def f3(): print(get_func()) f3() #  

 def wrapper(func): functools.wraps(func) def wrapped(*args, **kwargs): return func(*args, **kwargs) return wrapped @wrapper def f4(): print(get_func()) f4() #  

 f5 = lambda: get_func() print(f5()) #  at 0x0000017235CB2950> 

Corrección de mi respuesta anterior, porque la comprobación de subdicto ya funciona con “<=" llamada en dict_items y las llamadas adicionales a set () dan como resultado problemas, si hay valores de dict que se dicten ellos mismos:

 import gc import inspect def get_func(): frame = inspect.currentframe().f_back code = frame.f_code return [ referer for referer in gc.get_referrers(code) if getattr(referer, "__code__", None) is code and inspect.getclosurevars(referer).nonlocals.items() <= frame.f_locals.items()][0]