Python 如何将当前函数转换为变量?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4492559/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 16:00:12  来源:igfitidea点击:

How to get current function into a variable?

pythonfunctionintrospection

提问by Ryan C. Thompson

How can I get a variable that contains the currently executing function in Python? I don't want the function's name. I know I can use inspect.stackto get the current function name. I want the actual callable object. Can this be done without using inspect.stackto retrieve the function's name and then evaling the name to get the callable object?

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

Edit:I have a reason to do this, but it's not even a remotely good one. I'm using placto parse command-line arguments. You use it by doing plac.call(main), which generates an ArgumentParser object from the function signature of "main". Inside "main", if there is a problem with the arguments, I want to exit with an error message that includes the help text from the ArgumentParser object, which means that I need to directly access this object by calling plac.parser_from(main).print_help(). It would be nice to be able to say instead: plac.parser_from(get_current_function()).print_help(), so that I am not relying on the function being named "main". Right now, my implementation of "get_current_function" would be:

编辑:我有这样做的理由,但它甚至不是一个很好的理由。我正在使用plac来解析命令行参数。您可以通过 do 来使用它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”的实现是:

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

But this implementation relies on the function having a name, which I suppose is not too onerous. I'm never going to do plac.call(lambda ...).

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

In the long run, it might be more useful to ask the author of plac to implement a print_help method to print the help text of the function that was most-recently called using plac, or something similar.

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

采纳答案by kindall

The stack frame tells us what code object we're in. If we can find a function object that refers to that code object in its __code__attribute, we have found the function.

堆栈帧告诉我们我们在哪个代码对象中。如果我们能找到在其__code__属性中引用该代码对象的函数对象,我们就找到了该函数。

Fortunately, we can ask the garbage collector which objects hold a reference to our code object, and sift through those, rather than having to traverse every active object in the Python world. There are typically only a handful of references to a code object.

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

Now, functions can share code objects, and do in the case where you return a function froma function, i.e. a closure. When there's more than one function using a given code object, we can't tell which function it is, so we return None.

现在,函数可以共享代码对象,并且在函数返回函数的情况下执行,即闭包。当有多个函数使用给定的代码对象时,我们无法分辨它是哪个函数,因此我们返回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

Some test cases:

一些测试用例:

def foo():
    return giveupthefunc()

zed = lambda: giveupthefunc()

bar, foo = foo, None

print bar()
print zed()

I'm not sure about the performance characteristics of this, but i think it should be fine for your use case.

我不确定它的性能特征,但我认为它应该适合您的用例。

回答by aaronasterling

I recently spent a lot of time trying to do something like this and ended up walking away from it. There's a lot of corner cases.

我最近花了很多时间尝试做这样的事情,但最终还是放弃了。有很多角落案例。

If you just want the lowest level of the call stack, you can just reference the name that is used in the defstatement. This will be bound to the function that you want through lexical closure.

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

For example:

例如:

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

mewill now refer to the function in question regardless of the scope that the function is called from so long as it is not redefined in the scope where the definition occurs. Is there some reason why this won't work?

me现在将引用有问题的函数,而不管从哪个范围调用该函数,只要它没有在定义发生的范围内重新定义。有什么原因这不起作用吗?

To get a function that is executing higher up the call stack, I couldn't think of anything that can be reliably done.

为了获得在调用堆栈更高层执行的函数,我想不出任何可以可靠完成的事情。

回答by jsbueno

The call stack does not keep a reference to the function itself - although the running frame as a reference to the code object that is the code associated to a given function.

调用堆栈不保留对函数本身的引用 - 尽管运行帧作为对代码对象的引用,该代码对象是与给定函数关联的代码。

(Functions are objects with code, and some information about their environment, such as closures, name, globals dictionary, doc string, default parameters and so on).

(函数是带有代码的对象,以及一些关于它们的环境的信息,例如闭包、名称、全局字典、文档字符串、默认参数等)。

Therefore if you are running a regular function, you are better of using its own name on the globals dictionary to call itself, as has been pointed out.

因此,如果你正在运行一个常规函数,你最好在 globals 字典中使用它自己的名字来调用自己,正如已经指出的那样。

If you are running some dynamic, or lambda code, in which you can't use the function name, the only solution is to rebuild another function object which re-uses thre currently running code object and call that new function instead.

如果您正在运行一些不能使用函数名称的动态或 lambda 代码,唯一的解决方案是重建另一个函数对象,该对象重新使用当前运行的代码对象并调用该新函数。

You will loose a couple of things, like default arguments, and it may be hard to get it working with closures (although it can be done).

你会丢失一些东西,比如默认参数,并且可能很难让它与闭包一起工作(尽管它可以完成)。

I have written a blog post on doing exactly that - calling anonymous functions from within themselves - I hope the code in there can help you:

我写了一篇关于这样做的博客文章 - 从内部调用匿名函数 - 我希望那里的代码可以帮助你:

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

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

On a side note: avoid the use o inspect.stack -- it is too slow, as it rebuilds a lot of information each time it is called. prefer to use inspect.currentframe to deal with code frames instead.

附带说明:避免使用 o inspect.stack ——它太慢了,因为每次调用它都会重建大量信息。更喜欢使用inspect.currentframe 来处理代码帧。

This may sounds complicated, but the code itself is very short - I am pasting it bellow. The post above contains more information on how this works.

这听起来可能很复杂,但代码本身很短——我把它贴在下面。上面的帖子包含有关其工作原理的更多信息。

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)

If you really need the original function itself, the "myself" function above could be made to search on some scopes (like the calling function global dictionary) for a function object which code object would match with the one retrieved from the frame, instead of creating a new function.

如果您确实需要原始函数本身,则可以使上面的“myself”函数在某些范围(如调用函数全局字典)上搜索函数对象,该函数对象的代码对象将与从框架中检索到的对象相匹配,而不是创建一个新函数。

回答by bukzor

This is what you asked for, as close as I can come. Tested in python versions 2.4, 2.6, 3.0.

这就是你所要求的,我尽可能接近。在 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()

Output:

输出:

Current function is <function main at 0x2aec09fe2ed8>
Current function is <function inner1 at 0x2aec09fe2f50>
Current function is <function inner2 at 0x2aec0a0635f0>

回答by Triptych

OK after reading the question and comments again, I think this is a decent test case:

OK 再次阅读问题和评论后,我认为这是一个不错的测试用例:

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

I tried solving it by using a decorator to temporarily clobber the global namespace and reassigning the function object to the original name of the function:

我尝试通过使用装饰器临时破坏全局命名空间并将函数对象重新分配给函数的原始名称来解决它:

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)

I'm sure I haven't thought through all the use cases. The biggest problem I see is the function object intentionally changing what its own name points to (an operation which would be overwritten by the decorator), but that should be ok as long as the recursive function doesn't redefine its own name in the middle of recursing.

我确定我还没有考虑过所有的用例。我看到的最大问题是函数对象故意改变它自己的名字指向的东西(一个会被装饰器覆盖的操作),但只要递归函数没有在中间重新定义它自己的名字就可以了的递归。

Still not sure I'd ever need to do this, but thinking about was interesting.

仍然不确定我是否需要这样做,但思考很有趣。

回答by kindall

Here's another possibility: a decorator that implicitly passes a reference to the called function as the first argument (similar to selfin bound instance methods). You have to decorate each function that you want to receive such a reference, but "explicit is better than implicit" as they say.

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

Of course, it has all the disadvantage of decorators: another function call slightly degrades performance, and the signature of the wrapped function is no longer visible.

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

import functools

def gottahavethatfunc(func):

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

    return wrapper

The test case illustrates that the decorated function still gets the reference to itself even if you change the name to which the function is bound. This is because you're only changing the binding of the wrapper function. It also illustrates its use with a lambda.

测试用例说明,即使您更改了函数绑定的名称,装饰函数仍会获得对自身的引用。这是因为您只是在更改包装函数的绑定。它还说明了它与 lambda 的用法。

@gottahavethatfunc
def quux(me):
    return me

zoom = gottahavethatfunc(lambda me: me)

baz, quux = quux, None

print baz()
print zoom()

When using this decorator with an instance or class method, the method should accept the function reference as the first argument and the traditional selfas the second.

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

class Demo(object):

    @gottahavethatfunc
    def method(me, self):
        return me

print Demo().method()

The decorator relies on a closure to hold the reference to the wrapped function in the wrapper. Creating the closure directly might actually be cleaner, and won't have the overhead of the extra function call:

装饰器依赖一个闭包来保存对包装器中被包装函数的引用。直接创建闭包实际上可能更干净,并且不会有额外函数调用的开销:

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

Within the inner function, the name my_funcalways refers to that function; its value does not rely on a global name that may be changed. Then we just "lift" that function to the global namespace, replacing the reference to the outer function. Works in a class too:

在内部函数中,名称my_func总是指那个函数;它的值不依赖于可以更改的全局名称。然后我们只是将该函数“提升”到全局命名空间,替换对外部函数的引用。也在课堂上工作:

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

回答by Florian

I just define in the beginning of each function a "keyword" which is just a reference to the actual name of the function. I just do this for any function, if it needs it or not:

我只是在每个函数的开头定义了一个“关键字”,它只是对函数实际名称的引用。我只是为任何功能执行此操作,无论是否需要:

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

回答by muodov

sys._getframe(0).f_codereturns exactly what you need: the codeobject being executed. Having a code object, you can retrieve a name with codeobject.co_name

sys._getframe(0).f_code准确返回您需要的内容:正在执行的代码对象。拥有代码对象,您可以使用codeobject.co_name检索名称

回答by someone

Here a variation (Python 3.5.1) of the get_referrers() answer, which tries to distinguish between closures that are using the same code object:

这是 get_referrers() 答案的变体(Python 3.5.1),它试图区分使用相同代码对象的闭包:

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>

回答by someone

Correction of my previous answer, because the subdict check already works with "<=" called on dict_items and the additional set() calls result in problems, if there are dict-values which are dicts themself:

更正我之前的答案,因为 subdict 检查已经与 "<=" 调用 dict_items 和额外的 set() 调用导致问题,如果有字典值本身就是字典:

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]