Search code examples
pythonfunctionintrospection

How to get current function into a variable?


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.stack to get the current function name. I want the actual callable object. Can this be done without using inspect.stack to retrieve the function's name and then evaling the name to get the callable object?

Edit: I have a reason to do this, but it's not even a remotely good one. I'm using plac to 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:

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

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.


Solution

  • 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.

    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.

    Now, functions can share code objects, and do in the case where you return a function from a 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.

    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 funcs:
                        return None
                    funcs.append(func)
        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.