关于python:如何将当前函数转换为变量?

How to get current function into a variable?

如何在Python中获取包含当前正在执行的函数的变量?我不想要这个功能的名字。我知道我可以用inspect.stack来获取当前的函数名。我想要实际的可调用对象。是否可以在不使用inspect.stack检索函数名称然后eval获取可调用对象的情况下完成此操作?

编辑:我有理由这样做,但它甚至不是一个好的。我正在使用plac来解析命令行参数。您可以通过执行plac.call(main)来使用它,它从"main"的函数签名生成ArgumentParser对象。在"main"中,如果参数有问题,我想退出一个包含ArgumentParser对象的帮助文本的错误消息,这意味着我需要通过调用plac.parser_from(main).print_help()直接访问该对象。能够说:plac.parser_from(get_current_function()).print_help()会很高兴,所以我不依赖于名为"main"的函数。现在,我的"get_current_function"实现将是:

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

但是这个实现依赖于具有名称的函数,我认为这不是太繁重。我永远不会做plac.call(lambda ...)

从长远来看,要求plac的作者实现print_help方法来打印最近使用plac或类似东西调用的函数的帮助文本可能更有用。


堆栈框架告诉我们我们所处的代码对象。如果我们可以在其__code__属性中找到引用该代码对象的函数对象,我们就找到了该函数。

幸运的是,我们可以向垃圾收集器询问哪些对象持有对我们的代码对象的引用,并对这些对象进行筛选,而不是必须遍历Python世界中的每个活动对象。通常只有少数对代码对象的引用。

现在,函数可以共享代码对象,并且在从函数返回函数的情况下执行,即闭包。当使用给定的代码对象有多个函数时,我们无法判断它是哪个函数,所以我们返回None

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>


我最近花了很多时间尝试做这样的事情并最终离开它。有很多角落案例。

如果您只想要调用堆栈的最低级别,则只需引用def语句中使用的名称即可。这将通过词法闭包绑定到您想要的功能。

例如:

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

me现在将引用相关函数,无论调用函数的范围如何,只要它不在定义发生的范围内重新定义。有什么理由说这不起作用?

为了获得一个在调用堆栈中执行更高功能的函数,我想不出任何可以可靠地执行的操作。


这是另一种可能性:装饰器隐式地将对被调用函数的引用作为第一个参数传递(类似于绑定实例方法中的self)。你必须装饰你想要接收这样一个引用的每个函数,但正如他们所说的那样,"显式优于隐式"。

当然,它具有装饰器的所有缺点:另一个函数调用会略微降低性能,并且包装函数的签名不再可见。

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()

当使用带有实例或类方法的装饰器时,该方法应该接受函数引用作为第一个参数,传统的self作为第二个参数。

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()

在内部函数中,名称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]