How to print function arguments in sys.settrace?

You can use the combination of Code Objects and Frame Objects.

See for the descriptions of these in the Python Data-Model Reference.

import sys

def fn(frame, msg, arg):
    if msg != 'call': return
    # Filter as appropriate
    if frame.f_code.co_filename.startswith("/usr"): return
    print("Called", frame.f_code.co_name)
    for i in range(frame.f_code.co_argcount):
        name = frame.f_code.co_varnames[i]
        print("    Argument", name, "is", frame.f_locals[name])

sys.settrace(fn)

def hai(a, b, c):
    print(a, b, c)

hai("Hallo", "Welt", "!")

The crucial thing to realize is that

  1. we can see all local variables in the frame as f_locals.
  2. We can extract the names of the variables in the parameter list from f_code.co_varnames.

I turned Marcs answer into a script which can be used for inspecting other scripts:

print_func_calls.py:

#!/usr/bin/env python

import sys

# opt-out file names which start with one of these prefixes
FILENAME_FILTER = {"/usr", "<"}
# opt-in file names again which match one of these prefixes
FILENAME_UNFILTER = {"/lib/python/some-important-module"}
# opt-out function names
FN_NAME_FILTER = {"<module>", "__new__", "__setattr__", "<lambda>"}


def to_str(exp):
    """Turn an argument value into a string without dying on exceptions"""
    try:
        return repr(exp)[:100]
    except Exception as exc:
        return "n/a"


def fn(frame, msg, arg):
    if msg != 'call':
        return

    filename, fn_name = frame.f_code.co_filename, frame.f_code.co_name

    if (not all(not filename.startswith(p) for p in FILENAME_FILTER) and 
            all(not filename.startswith(p) for p in FILENAME_UNFILTER) or
            fn_name in FN_NAME_FILTER):
        return

    argstr = ", ".join("%s=%s" % (
            frame.f_code.co_varnames[i], to_str(frame.f_locals[frame.f_code.co_varnames[i]]))
            for i in range(frame.f_code.co_argcount))

    print(">>> %s::\033[37m%s\033[0m(%s)" % (filename, fn_name, argstr))


sys.settrace(fn)

sys.argv = sys.argv[1:]
exec(open(sys.argv[0]).read())

Use it like this:

print_func_calls.py my-script.py arg1..argN

Tags:

Python