Search code examples
pythonstatic-methodspython-inspect

Get the caller function object when it's a static method


I thought I knew a bit of Python, but then I stumped on this (a distilled form follows):

class A:

    @staticmethod
    def f():
        print('some code here to report exactly 
               who called A.f()')

class B:

    @staticmethod
    def f():
        A.f()

class C:

    @staticmethod
    def f():
        A.f()

B.f() # I want this to print 'B.f'
C.f() # I want this to print 'C.f'

Note: compared to How can I get the address of the caller function?, this question describes a much more difficult situation (in my opinion). In fact, the referred question is easily solvable using an iteration over globals (see below).

Here's what I tried:

  1. If I use the inspect module I get access to the the caller function's name as a str (not the caller function's object itself), and so I can't use the __qualname__.

  2. If I try to iterate over the globals to see which global object has a method named 'f' I get two answers with no way to decide between the two.

  3. I thought that an attribute 'cls' should be among the locals, just like 'self' would be if these were regular functions (not static methods), and if so I could get to the class by inspect.stack()[1][0].f_locals['cls'], but 'cls' is not there.

Any ideas?

UPD1: I keep digging and so far approach 1) seems the most promising: as soon as I get hold of the caller function object (not just the name, but the function itself) I'm all set. However, I don't see a way to do that in the setup above. One way around this would be to add an argument to A.f() and make all caller functions pass a copy of themselves: A.f(caller_func = Caller_Class.f). But it seems like double accounting. So, any ideas are still appreciated.


Solution

  • You were on the right track with your point 1. in your question: with inspect, you can get caller function's name as a str (which is useless to get the qualified name), but you ignored that the frame object also has the attribute f_code, which is of type codeobject and that recently in python 3.11 got the attribute co_qualname, exactly the fully qualified function name at the given frame.

    So, as simple as:

    class A:
        @staticmethod
        def f():
            print(inspect.currentframe().f_back.f_code.co_qualname) 
    
    B.f() # prints B.f
    C.f() # prints C.f
    

    You can see more details here.