Search code examples
pythondebuggingpdb

How can I step to use the python debugger to break at every function call?


I want to closely monitor the chain of function calls which are called from a certain function.

import pdb; pdb.set_trace()
res = api.InsertVideoEntry(video_entry, video)

I'm looking for a way to easily see that api.insertVideoEntry(video_entry, video) calls foo() which calls bar() which calls baz(),

Here's a really crude diagram to show what I mean. I don't need it in this form, but this is the kind kind of information I'm looking for.

api.insertVideoEntry()
    foo()
        bar()
            baz()
            baz2()
        log()
    finish()

Solution

  • This was an interesting learning experience to write up. Maybe you can use the code shown here? This demonstration should give you an idea of the type of output you can expect when using trace.

    # Functions to trace
    # ==================
    
    def baz():
        pass
    
    def baz2():
        pass
    
    def bar():
        baz()
        baz2()
    
    def log():
        pass
    
    def foo():
        bar()
        log()
    
    def finish():
        pass
    
    def insertVideoEntry():
        foo()
        finish()
    
    # Names to trace
    # ==============
    
    names = list(locals())
    
    # Machinery for tracing
    # =====================
    
    import os
    import sys
    
    def trace(start, *names):
        def tracefunc(frame, event, arg):
            if event == 'call':
                code = frame.f_code
                name = code.co_name
                if name in names:
                    level = -start
                    while frame:
                        frame = frame.f_back
                        level += 1
                    print('{}{}.{}()'.format(
                        '    ' * level,
                        os.path.splitext(os.path.basename(code.co_filename))[0],
                        name))
                    return tracefunc
        sys.settrace(tracefunc)
    
    # Demonstration of tracing
    # ========================
    
    trace(2, *names)
    
    insertVideoEntry()
    

    If you are interested in a recursive demo, you might like this variation with a called arguments readout:

    import os
    import sys
    
    def main(discs):
        a, b, c = list(range(discs, 0, -1)), [], []
        line = '-' * len(repr(a))
        print(a, b, c, sep='\n')
        for source, destination in towers_of_hanoi(discs, a, b, c):
            destination.append(source.pop())
            print(line, a, b, c, sep='\n')
    
    def towers_of_hanoi(count, source, via, destination):
        if count > 0:
            count -= 1
            yield from towers_of_hanoi(count, source, destination, via)
            yield source, destination
            yield from towers_of_hanoi(count, via, source, destination)
    
    def trace(start, *names):
        def tracefunc(frame, event, arg):
            if event == 'call':
                code = frame.f_code
                name = code.co_name
                if name in names:
                    level = -start
                    args = ', '.join(repr(frame.f_locals[name]) for name in
                                     code.co_varnames[:code.co_argcount])
                    while frame:
                        frame = frame.f_back
                        level += 1
                    print('{}{}.{}({})'.format(
                        ' ' * (level * 4),
                        os.path.splitext(os.path.basename(code.co_filename))[0],
                        name, args))
                    return tracefunc
        sys.settrace(tracefunc)
    
    if __name__ == '__main__':
        trace(3, 'main', 'towers_of_hanoi')
        main(3)