code python function introspection

code - Código Python para obtener la función actual en una variable?



inspect python 3 (10)

¿Cómo puedo obtener una variable que contiene 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 que se puede llamar. ¿Se puede hacer esto sin utilizar inspect.stack para recuperar el nombre de la función y luego eval el nombre para obtener el objeto invocable?

Editar: Tengo una razón para hacer esto, pero ni siquiera es remotamente buena. Estoy usando plac para analizar argumentos de línea de comandos. Lo 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 incluya 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() , por lo que no estoy confiando en que la función se denomine "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 que la función tenga un nombre, que supongo que no es demasiado oneroso. 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 mediante plac, o algo similar.


Acabo de definir 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


Aquí hay otra posibilidad: un decorador que pasa implícitamente una referencia a la función llamada como primer argumento (similar a self en métodos de instancia enlazados). Debe decorar cada función que desea recibir tal referencia, pero "explícita es mejor que implícita", como dicen.

Por supuesto, tiene todas las desventajas de los decoradores: otra llamada de función degrada ligeramente 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 una lambda.

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

Al usar 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 el 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 podría 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 depende de un nombre global que pueda ser cambiado. Luego simplemente "elevamos" esa función al espacio de nombre 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()


Aquí hay una variación (Python 3.5.1) de la respuesta get_referrers (), que intenta distinguir entre cierres que usan 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) # <function f1.<locals>.f2 at 0x0000017235CB2C80> # 4 f_var2 = f1(2) f_var2(3) # <function f1.<locals>.f2 at 0x0000017235CB2BF8> # 5

def f3(): print(get_func()) f3() # <function f3 at 0x0000017235CB2B70>

def wrapper(func): functools.wraps(func) def wrapped(*args, **kwargs): return func(*args, **kwargs) return wrapped @wrapper def f4(): print(get_func()) f4() # <function f4 at 0x0000017235CB2A60>

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


Corrección de mi respuesta anterior, porque el control de subíndice ya funciona con "<=" llamado en dict_items y las llamadas set () adicionales producen problemas, si hay valores dict que son dicts themself:

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]


El marco de pila nos dice en qué objeto de código estamos. Si podemos encontrar un objeto de función que func_code referencia a ese objeto de código en su atributo func_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 tamizarlos, en lugar de tener que atravesar todos los objetos activos en el mundo de Python. Normalmente hay solo un puñado de referencias a un objeto de código.

Ahora, las funciones pueden compartir objetos de código y hacerlo en el caso en que devuelva una función de una función, es decir, un cierre. Cuando hay más de una función usando un objeto de código dado, no podemos decir qué función es, así 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, "func_code", None) is code: if getattr(func, "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 llegar. 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 <function main at 0x2aec09fe2ed8> Current function is <function inner1 at 0x2aec09fe2f50> Current function is <function inner2 at 0x2aec0a0635f0>


La pila de llamadas no mantiene una referencia a la función en sí misma, aunque la trama en ejecución es una 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, nombre, 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 un código dinámico o lambda, en el cual no puede usar el nombre de la función, la única solución es reconstruir otro objeto de función que reutilice el objeto de código actualmente en ejecución y llame a esa nueva función.

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

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

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 una gran cantidad de información cada vez que se llama. Prefiere usar inspect.currentframe para tratar con marcos de código en su lugar.

Esto puede sonar complicado, pero el código en sí es muy corto: lo estoy pegando debajo. 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 podría hacerse para buscar en algunos ámbitos (como el diccionario global de la función de llamada) para 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.


OK después de leer nuevamente la pregunta y los comentarios, 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 usando un decorador para tapar temporalmente el espacio de nombre global y reasignar el objeto de la 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 problema más grande que veo es el objeto de función cambiando intencionalmente lo que su propio nombre señala (una operación que sería sobrescrita por el decorador), pero eso debería estar bien siempre que la función recursiva no redefina su propio nombre en el medio de recurrir

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


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 pila de llamadas, puede simplemente hacer referencia al nombre que se utiliza en la declaración de def . Esto estará ligado a la función que desee a través del cierre léxico.

Por ejemplo:

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

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

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


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