How to get current function into a variable?
如何在Python中获取包含当前正在执行的函数的变量?我不想要这个功能的名字。我知道我可以用
编辑:我有理由这样做,但它甚至不是一个好的。我正在使用plac来解析命令行参数。您可以通过执行
1 2 3 | import inspect def get_current_function(): return eval(inspect.stack()[1][3]) |
但是这个实现依赖于具有名称的函数,我认为这不是太繁重。我永远不会做
从长远来看,要求plac的作者实现print_help方法来打印最近使用plac或类似东西调用的函数的帮助文本可能更有用。
堆栈框架告诉我们我们所处的代码对象。如果我们可以在其
幸运的是,我们可以向垃圾收集器询问哪些对象持有对我们的代码对象的引用,并对这些对象进行筛选,而不是必须遍历Python世界中的每个活动对象。通常只有少数对代码对象的引用。
现在,函数可以共享代码对象,并且在从函数返回函数的情况下执行,即闭包。当使用给定的代码对象有多个函数时,我们无法判断它是哪个函数,所以我们返回
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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 |
一些测试用例:
1 2 3 4 5 6 7 8 9 | def foo(): return giveupthefunc() zed = lambda: giveupthefunc() bar, foo = foo, None print bar() print zed() |
我不确定这个的性能特征,但我认为它对你的用例应该没问题。
这就是你要求的,尽可能接近我。在python版本2.4,2.6,3.0中测试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | #!/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() |
输出:
1 2 3 | Current function is <function main at 0x2aec09fe2ed8> Current function is <function inner1 at 0x2aec09fe2f50> Current function is <function inner2 at 0x2aec0a0635f0> |
我最近花了很多时间尝试做这样的事情并最终离开它。有很多角落案例。
如果您只想要调用堆栈的最低级别,则只需引用
例如:
1 2 | def recursive(*args, **kwargs): me = recursive |
为了获得一个在调用堆栈中执行更高功能的函数,我想不出任何可以可靠地执行的操作。
这是另一种可能性:装饰器隐式地将对被调用函数的引用作为第一个参数传递(类似于绑定实例方法中的
当然,它具有装饰器的所有缺点:另一个函数调用会略微降低性能,并且包装函数的签名不再可见。
1 2 3 4 5 6 7 8 9 | import functools def gottahavethatfunc(func): @functools.wraps(func) def wrapper(*args, **kwargs): return func(func, *args, **kwargs) return wrapper |
测试用例说明,即使您更改了函数绑定的名称,装饰函数仍会获得对自身的引用。这是因为您只是更改了包装函数的绑定。它还说明了它与lambda的使用。
1 2 3 4 5 6 7 8 9 10 | @gottahavethatfunc def quux(me): return me zoom = gottahavethatfunc(lambda me: me) baz, quux = quux, None print baz() print zoom() |
当使用带有实例或类方法的装饰器时,该方法应该接受函数引用作为第一个参数,传统的
1 2 3 4 5 6 7 | class Demo(object): @gottahavethatfunc def method(me, self): return me print Demo().method() |
装饰器依赖于一个闭包来保存对包装器中包装函数的引用。直接创建闭包可能实际上更干净,并且不会有额外函数调用的开销:
1 2 3 4 5 | def my_func(): def my_func(): return my_func return my_func my_func = my_func() |
在内部函数中,名称
1 2 3 4 5 6 | class K(object): def my_method(): def my_method(self): return my_method return my_method my_method = my_method() |
我只是在每个函数的开头定义一个"关键字",它只是对函数实际名称的引用。我只是为任何函数执行此操作,如果它需要或不需要:
1 2 3 4 5 6 7 | def test(): this=test if not hasattr(this,'cnt'): this.cnt=0 else: this.cnt+=1 print this.cnt |
调用堆栈不保留对函数本身的引用 -
虽然运行帧作为对代码对象的引用,即与给定函数关联的代码。
(函数是包含代码的对象,以及有关其环境的一些信息,例如闭包,名称,全局字典,doc字符串,默认参数等)。
因此,如果您正在运行常规函数,则最好在全局字典上使用自己的名称来调用自身,正如已经指出的那样。
如果您正在运行一些不能使用函数名的动态或lambda代码,唯一的解决方案是重建另一个函数对象,该对象重用当前运行的代码对象并调用该新函数。
你将失去一些东西,比如默认参数,并且可能很难让它使用闭包(尽管可以这样做)。
我写了一篇关于正是这样做的博客文章 - 从内部调用匿名函数 - 我希望那里的代码可以帮助你:
http://metapython.blogspot.com/2010/11/recursive-lambda-functions.html
旁注:避免使用inspect.stack - 它太慢了,因为它每次调用时都会重建大量信息。我更喜欢使用inspect.currentframe来处理代码帧。
这可能听起来很复杂,但代码本身很短 - 我正在将它贴在下面。上面的帖子包含有关其工作原理的更多信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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) |
如果你真的需要原始函数本身,上面的"我自己"函数可以在一些范围(如调用函数全局字典)上搜索一个函数对象,该函数对象与从框架中检索到的代码对象匹配,而不是创建一个新功能。
sys._getframe(0).f_code完全返回您需要的内容:正在执行的代码对象。拥有代码对象,您可以使用codeobject.co_name检索名称
这里是get_referrers()答案的变体(Python 3.5.1),它试图区分使用相同代码对象的闭包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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 |
1 2 3 4 5 | def f3(): print(get_func()) f3() # <function f3 at 0x0000017235CB2B70> |
1 2 3 4 5 6 7 8 9 10 11 12 | 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> |
1 2 3 4 | f5 = lambda: get_func() print(f5()) # <function <lambda> at 0x0000017235CB2950> |
在再次阅读问题和评论后,我认为这是一个不错的测试用例:
1 2 3 4 5 6 7 8 | 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 |
我尝试使用装饰器暂时破坏全局命名空间并将函数对象重新分配给函数的原始名称来解决它:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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) |
我确定我没有考虑过所有用例。我看到的最大问题是函数对象故意改变它自己的名字指向的内容(一个将被装饰器覆盖的操作),但只要递归函数不在中间重新定义它自己的名字就应该没问题。递归
仍然不确定我是否需要这样做,但思考很有趣。
更正我以前的答案,因为子句检查已经与dict_items上调用的"<="一起使用,并且附加的set()调用会导致问题,如果有dict-values自己的话:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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] |