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