Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get function callers' information in python

I want to get information about the callers of a specific function in python. For example:

class SomeClass():
    def __init__(self, x):
        self.x = x
    def caller(self):
        return special_func(self.x)

def special_func(x):
    print "My caller is the 'caller' function in an 'SomeClass' class."

Is it possible with python?

like image 455
iTayb Avatar asked Aug 03 '12 16:08

iTayb


3 Answers

Yes, the sys._getframe() function let's you retrieve frames from the current execution stack, which you can then inspect with the methods and documentation found in the inspect module; you'll be looking for specific locals in the f_locals attribute, as well as for the f_code information:

import sys
def special_func(x):
    callingframe = sys._getframe(1)
    print 'My caller is the %r function in a %r class' % (
        callingframe.f_code.co_name, 
        callingframe.f_locals['self'].__class__.__name__)

Note that you'll need to take some care to detect what kind of information you find in each frame.

sys._getframe() returns a frame object, you can chain through the whole stack by following the f_back reference on each. Or you can use the inspect.stack() function to produce a lists of frames with additional information.

like image 118
Martijn Pieters Avatar answered Sep 23 '22 17:09

Martijn Pieters


An example:

def f1(a):
    import inspect
    print 'I am f1 and was called by', inspect.currentframe().f_back.f_code.co_name
    return a

def f2(a):
    return f1(a)

Will retrieve the "immediate" caller.

>>> f2(1)
I am f1 and was called by f2

And if wasn't called from another you get (in IDLE):

>>> f1(1)
I am f1 and was called by <module>
like image 29
Jon Clements Avatar answered Sep 23 '22 17:09

Jon Clements


Thanks to Jon Clements answer I was able to make a function that returns an ordered list of all callers:

def f1():
    names = []
    frame = inspect.currentframe()
    ## Keep moving to next outer frame
    while True:
        try:
            frame = frame.f_back
            name = frame.f_code.co_name
            names.append(name)
        except:
            break
    return names

and when called in a chain:

def f2():
    return f1()

def f3():
    return f2()

def f4():
    return f3()

print f4()

looks like this:

['f2', 'f3', 'f4', '<module>']

In my case I filter out anything at '<module>' and after, and then take the last item to be the name of the originating caller.

Or modify the original loop to bail at the first appearance of any name starting with '<':

frame = frame.f_back
name = frame.f_code.co_name
if name[0] == '<':
    break
names.append(name)
like image 38
Zach Young Avatar answered Sep 20 '22 17:09

Zach Young