from tracefunc import tracefunc
from pprint import pprinttracefunc
tracefunc takes a function and its arguments, executes it, and returns a list of per-call traces. Each entry is (stack_str, trace_dict) where stack_str is the call stack (filtered so fn is the shallowest frame shown; empty when target_func is None) and trace_dict maps AST-level snippets to (hit_count, vars_map) with per-hit samples. Comprehensions show up as their own lines with per-iteration values.
Install
pip install tracefuncRequires Python 3.12+ (uses sys.monitoring instruction events).
How to use
Simple function
Here’s a simple example tracing a loop:
def demo(n):
total = 0
for i in range(n): total += i
return totaldef show_res(x):
for snippet, (hits, vars_map) in x.items():
print('-', repr(snippet), hits)
pprint(vars_map)stack, result = tracefunc(demo, 3, target_func=demo)[0]
print(stack)
show_res(result)demo (2643322203.py:1)
- 'total = 0' 1
{'total': [('int', '0')]}
- 'for i in range(n):' 4
{'i': [('int', '0'), ('int', '1'), ('int', '2'), ('int', '2')],
'n': [('int', '3'), ('int', '3'), ('int', '3'), ('int', '3')],
'range': [('type', "<class 'range'>"),
('type', "<class 'range'>"),
('type', "<class 'range'>"),
('type', "<class 'range'>")]}
- 'total += i' 3
{'i': [('int', '0'), ('int', '1'), ('int', '2')],
'total': [('int', '0'), ('int', '1'), ('int', '3')]}
- 'return total' 1
{'total': [('int', '3')]}
Multiple statements on one physical line
Semicolon-separated statements are tracked separately.
def one_liner(): x = 1; y = 2; return x + y
_, res = tracefunc(one_liner)[0]
show_res(res)- 'x = 1' 1
{'x': [('int', '1')]}
- 'y = 2' 1
{'y': [('int', '2')]}
- 'return x + y' 1
{'x': [('int', '1')], 'y': [('int', '2')]}
Targeted tracing and call stacks
You can trace a specific target function and see the call stack for each call. Stack paths are shown relative to fn’s directory when possible.
def target(x):
return x + 1
def another(x): return target(x)
def wrapper(n):
out = []
for i in range(n): out.append(target(i))
out.append(another(10))
return out
for stack, res in tracefunc(wrapper, 2, target_func=target):
print(stack)
show_res(res)wrapper (1041865549.py:8)
target (1041865549.py:1)
- 'return x + 1' 1
{'x': [('int', '0')]}
wrapper (1041865549.py:8)
target (1041865549.py:1)
- 'return x + 1' 1
{'x': [('int', '1')]}
wrapper (1041865549.py:9)
another (1041865549.py:4)
target (1041865549.py:1)
- 'return x + 1' 1
{'x': [('int', '10')]}
Nested function
Nested definitions appear as statements, and their bodies are traced when called.
def outer(x):
def inner(y):
return x + y
return inner(5)
_, res = tracefunc(outer, 10)[0]
show_res(res)- 'def inner(y):' 1
{'inner': [('function', '<function outer.<locals>.inner>')]}
- 'return x + y' 1
{'x': [('int', '10')], 'y': [('int', '5')]}
- 'return inner(5)' 1
{'inner': [('function', '<function outer.<locals>.inner>')]}