0

For example, I have the following code:

def m1(num):
    pass


def m2(num):
    pass


def foo(num):
    m2(num)


def foo2(num):
    foo(num)
    m1(num)

def main():
    foo2(1)


if __name__ == "__main__":
    main()

Is there a way to get call list for each function? For example, for this example I want to get the following dict:

main: foo2
foo2: foo, m1
foo: m2
m2: 
m1: 

I know only one way to complete my task. It is to use globals() and then parse source code. But it seems is very ugly.

4
  • 1
    What you're looking to build is not a stack trace, but a call graph—sort of an upside-down stack trace. Commented Dec 18, 2014 at 7:12
  • Also, "I know only one way to complete my task. It is to use globals() and then parse source code." How do you think parsing source code would help? I can't see any solution where that would be useful. But if you can show that, I'll bet I could show you how to replace the parsing with something different. Commented Dec 18, 2014 at 7:12
  • @abarnert, globals()["file"] is path of our file. I think, that we can do something like that: f = open("globals()["__file__"]"); and then parse it. Commented Dec 18, 2014 at 7:14
  • Well, globals()["__file__"] gets you the exact same thing as just __file__, so globals isn't helping there… Also, you can use inspect.getsource to read source. Meanwhile, what would you do with the data once you parse it? You can use the ast module to create a parsed tree out of it, but then what? Are you just looking to create a static call graph—all the functions referenced in main's body—rather than a dynamic one—all the functions called during an actual run of the program? Commented Dec 18, 2014 at 7:17

1 Answer 1

1

I know only one way to complete my task. It is to use globals() and then parse source code. But it seems is very ugly.

I'm not sure what you think parsing source code would do for you here.


If you want to build a dynamic call graph, what you want to do is decorate each function—replace it with a wrapper that does some extra stuff before or after calling the real function. For example:

def wrapper(func):
    @functools.wraps
    def wrap(*args, **kwargs):
        # do stuff to update call graph
        return func(*args, **kwargs)
    return wrap

g = globals()
for name, value in g.items():
    if callable(value):
        g[name] = wrapper(value)

Or, maybe more simply (and more flexibly), you might want to use the bdb debugger framework to step through your program and just record the user_call instances.


If you want to build a static call graph, then obviously that comes from the source, but there's no reason to parse it yourself when Python comes with the ast module to do it for you. (You can use inspect.getsource to get the source code to parse.)

Sign up to request clarification or add additional context in comments.

3 Comments

is it right, that wrapper does not return anything? I have just an error: TypeError: 'NoneType' object is not callable.
@Denis: Sorry, yeah, you need to return wrap. Fixed. This is just a bog-standard decorator—even if you aren't using it like one (putting @wrapper before each def statement) you still define it the same way.
I am sorry, but do You test this code? I get error: AttributeError: 'int' object has no attribute '__module__' on line foo2(1). I try fix it, but I have no success.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.