8

I want to write a function which returns the calling function:

def foo():
    return get_calling_function() #should return 'foo' function object

There's numerous examples online how to get the calling function's name, but not how to get the actual object. I've come up with the following solution which gets the name, then looks it up in the calling function's global namespace. However this doesn't work for class functions since there you need the class name as well, and I image there's a bunch of other edge cases as well.

from inspect import stack
def get_calling_function():
    return stack()[2][0].f_globals[stack()[1][3]]

So any advice how or if its possible to write this function so that it works generically (on Python 3, btw)? Thanks.

marius
  • 1,352
  • 1
  • 13
  • 29
  • What is your use case? – Steven Rumbalski Aug 22 '16 at 12:40
  • I'm writing a function which returns a dict of all of the arguments passed to the function from which its called, *except* for ones whose values are set to their defaults. I need the calling function object so that I can call `getargspec` on it so that I can figure out what these defaults are. – marius Aug 22 '16 at 13:24
  • What? No, in that case you would need the *current* function object. – Karl Knechtel Oct 15 '22 at 05:52

1 Answers1

9

Calling can happen from any code object (and from an extension module/builtin): from exec, execfile, from module name space (during import), from within a class definition, from within a method / classmethod / staticmethod, from a decorated function/method, from within a nested function, ... - so there is no "calling function" in general, and the difficulty to do anything good with that.

The stack frames and their code objects are the most general you can get - and examine the attributes.


This one finds the calling function in many cases:

import sys, inspect

def get_calling_function():
    """finds the calling function in many decent cases."""
    fr = sys._getframe(1)   # inspect.stack()[1][0]
    co = fr.f_code
    for get in (
        lambda:fr.f_globals[co.co_name],
        lambda:getattr(fr.f_locals['self'], co.co_name),
        lambda:getattr(fr.f_locals['cls'], co.co_name),
        lambda:fr.f_back.f_locals[co.co_name], # nested
        lambda:fr.f_back.f_locals['func'],  # decorators
        lambda:fr.f_back.f_locals['meth'],
        lambda:fr.f_back.f_locals['f'],
        ):
        try:
            func = get()
        except (KeyError, AttributeError):
            pass
        else:
            if func.__code__ == co:
                return func
    raise AttributeError("func not found")
     
# Usage

def f():
    def nested_func():
        print get_calling_function()
    print get_calling_function()
    nested_func()

class Y:
    def meth(self, a, b=10, c=11):
        print get_calling_function()
        class Z:
            def methz(self):
                print get_calling_function()
        z = Z()
        z.methz()
        return z
    @classmethod
    def clsmeth(cls):
        print get_calling_function()
    @staticmethod
    def staticmeth():
        print get_calling_function()

f()
y = Y()
z = y.meth(7)
z.methz()
y.clsmeth()
##y.staticmeth()  # would fail

It finds:

<function f at 0x012E5670>
<function nested_func at 0x012E51F0>
<bound method Y.meth of <__main__.Y instance at 0x01E41580>>
<bound method Z.methz of <__main__.Z instance at 0x01E63EE0>>
<bound method Z.methz of <__main__.Z instance at 0x01E63EE0>>
<bound method classobj.clsmeth of <class __main__.Y at 0x01F3CF10>>

However, it may fail to find the function, or find the wrong function, for example, finding the wrong decorator-generated wrapper when the same decorator is used on multiple functions with the same name in different scopes.

user2357112
  • 260,549
  • 28
  • 431
  • 505
kxr
  • 4,841
  • 1
  • 49
  • 32
  • That's a fair point, my function can just Exception out in those undefined cases you mention. But if it *was* called from a function, either module or class level, then I want the function object of the caller. I thus-far have failed to find this object anywhere inside those stack objects. – marius Aug 22 '16 at 12:06
  • There is no regular uniform link to a possibly defining function for the code objects on the frame stack. I've added a finder demo which matches for many typical cases - matches verified by `func.__code__ == co`. That could be extended ... – kxr Aug 23 '16 at 20:16
  • Wow, that's perfect, many thanks! Certainly covers all the cases I'm interested in. – marius Aug 23 '16 at 21:05