Wednesday, September 30, 2015

Find the caller and caller's caller of a Python function

By Vasudev Ram


While browsing some Python posts on the Net, I saw one that made use of the sys._getframe() function. I had seen that function before, and remembered that it returns information about currently active stack frames. So I looked up the docs for sys._getframe(), thinking that it might be possible to use it within a function, to find that function's caller. Turned out that it could. I also googled for terms like "find the name of the calling function in python", and found two relevant Stack Overflow (SO) posts:

Getting the caller function name inside another function in Python?

Python: How to get the caller's method name in the called method?

It can be done by using either inspect.stack() or sys._getframe(). (The inspect module in Python's standard library supports that (finding a function's caller via stack frames), as well as some other useful introspection techniques.)

(I had blogged a couple of times about inspect, earlier, here:

Python's inspect module is powerful

and here:

Using inspect.getargvalues to debug Python programs

)

So I tried out the SO code examples and slightly modified them to make these two small programs that print the name of the caller, and the caller's caller.

Here is the first program:
'''
File: find_callers.py
Run with: python find_callers.py
'''

import sys

def foo():
    print "I am foo, calling bar:"
    bar()

def bar():
    print "I am bar, calling baz:"
    baz()

def baz():
    print "I am baz:"
    caller = sys._getframe(1).f_code.co_name
    callers_caller = sys._getframe(2).f_code.co_name
    print "I was called from", caller
    print caller, "was called from", callers_caller

foo()
When run, this program outputs:
I am foo, calling bar:
I am bar, calling baz:
I am baz:
I was called from bar
bar was called from foo

The second program is similar to the one above, except that, just for fun and to do it differently, I defined part of the overall code expression as a string, interpolated the argument (2 or 3) into it using Python string formatting, and then eval()'ed the resulting string. Note that since there is now an additional function call (eval), I had to change the arguments to sys._getframe() from 1 and 2 to 2 and 3:
# File: find_callers_with_eval.py
# Run with: python find_callers_with_eval.py

import sys

getframe_expr = 'sys._getframe({}).f_code.co_name'

def foo():
    print "I am foo, calling bar:"
    bar()

def bar():
    print "I am bar, calling baz:"
    baz()

def baz():
    print "I am baz:"
    caller = eval(getframe_expr.format(2))
    callers_caller = eval(getframe_expr.format(3))
    print "I was called from", caller
    print caller, "was called from", callers_caller

foo()
The output of this second program was identical to the first.

Note that the Python documentation says:

[ CPython implementation detail: This function should be used for internal and specialized purposes only. It is not guaranteed to exist in all implementations of Python. ]

Also, use eval() with care, and only on trusted code.

- Vasudev Ram - Online Python training and programming

Dancing Bison Enterprises

Signup to hear about new products and services that I create.

Posts about Python  Posts about xtopdf

No comments: