The trace module tracks which lines of code execute. Use it for debugging, coverage analysis, and understanding program flow.
Basic Tracing
# Trace execution of a script
python -m trace --trace script.py
# Output shows each line as it executes:
# --- modulename: script, funcname: <module>
# script.py(1): x = 1
# script.py(2): y = 2
# script.py(3): print(x + y)Count Line Execution
# Count how many times each line executes
python -m trace --count script.py
# Creates script.cover file with countsCoverage Report
# Generate coverage report
python -m trace --count --report --coverdir=coverage script.py
# Shows:
# - Lines executed
# - Lines not executed (marked with >>>>>>>)Programmatic Tracing
import trace
import sys
# Create tracer
tracer = trace.Trace(
count=True,
trace=False,
countfuncs=True,
countcallers=True
)
# Run code with tracing
def my_function():
x = 1
y = 2
return x + y
tracer.runfunc(my_function)
# Get results
results = tracer.results()
results.write_results(show_missing=True, coverdir='coverage')Trace Specific Modules
# Only trace specific module
python -m trace --trace --ignore-dir=/usr/lib script.py
# Ignore standard library
python -m trace --trace --ignore-module=os,sys script.pyCustom Trace Function
import sys
def trace_calls(frame, event, arg):
if event == 'call':
filename = frame.f_code.co_filename
funcname = frame.f_code.co_name
lineno = frame.f_lineno
print(f"Call: {funcname} in {filename}:{lineno}")
return trace_calls
def trace_lines(frame, event, arg):
if event == 'line':
filename = frame.f_code.co_filename
lineno = frame.f_lineno
print(f"Line: {filename}:{lineno}")
return trace_lines
# Enable tracing
sys.settrace(trace_calls)
# Your code here
def example():
x = 1
y = 2
return x + y
example()
# Disable tracing
sys.settrace(None)Trace Events
import sys
def comprehensive_trace(frame, event, arg):
"""Handle all trace events."""
co = frame.f_code
func_name = co.co_name
line_no = frame.f_lineno
filename = co.co_filename
if event == 'call':
print(f"CALL: {func_name}() at {filename}:{line_no}")
return comprehensive_trace
elif event == 'line':
print(f"LINE: {filename}:{line_no}")
return comprehensive_trace
elif event == 'return':
print(f"RETURN: {func_name}() => {arg}")
return comprehensive_trace
elif event == 'exception':
exc_type, exc_value, exc_tb = arg
print(f"EXCEPTION: {exc_type.__name__}: {exc_value}")
return comprehensive_trace
return comprehensive_trace
sys.settrace(comprehensive_trace)Trace Class for Reuse
import sys
from contextlib import contextmanager
class Tracer:
def __init__(self, trace_lines=True, trace_calls=True):
self.trace_lines = trace_lines
self.trace_calls = trace_calls
self.call_stack = []
def trace(self, frame, event, arg):
if event == 'call' and self.trace_calls:
self.call_stack.append(frame.f_code.co_name)
indent = ' ' * len(self.call_stack)
print(f"{indent}-> {frame.f_code.co_name}()")
elif event == 'return':
if self.call_stack:
self.call_stack.pop()
elif event == 'line' and self.trace_lines:
indent = ' ' * len(self.call_stack)
print(f"{indent} line {frame.f_lineno}")
return self.trace
@contextmanager
def tracing(self):
sys.settrace(self.trace)
try:
yield
finally:
sys.settrace(None)
# Usage
tracer = Tracer(trace_lines=False)
with tracer.tracing():
my_function()Coverage with trace Module
import trace
def calculate_coverage(module_name: str):
"""Calculate code coverage for a module."""
tracer = trace.Trace(
count=True,
trace=False,
)
# Import and run tests
import importlib
module = importlib.import_module(module_name)
if hasattr(module, 'run_tests'):
tracer.runfunc(module.run_tests)
# Get coverage results
results = tracer.results()
# Analyze
for filename, counts in results.counts.items():
if module_name in filename:
executed = len([c for c in counts.values() if c > 0])
total = len(counts)
percent = (executed / total) * 100 if total else 0
print(f"{filename}: {percent:.1f}% ({executed}/{total} lines)")Call Graph Generation
import sys
class CallGraphTracer:
def __init__(self):
self.calls = []
self.current_caller = None
def trace(self, frame, event, arg):
if event == 'call':
callee = frame.f_code.co_name
if self.current_caller:
self.calls.append((self.current_caller, callee))
self.current_caller = callee
elif event == 'return':
# Pop back to caller
if frame.f_back:
self.current_caller = frame.f_back.f_code.co_name
return self.trace
def get_graph(self):
return self.calls
tracer = CallGraphTracer()
sys.settrace(tracer.trace)
# Run code
sys.settrace(None)
# Print call graph
for caller, callee in tracer.get_graph():
print(f"{caller} -> {callee}")Trace with Filtering
import sys
import os
class FilteredTracer:
def __init__(self, include_patterns=None, exclude_patterns=None):
self.include = include_patterns or []
self.exclude = exclude_patterns or ['<', '/usr/', '/lib/']
def should_trace(self, filename):
for pattern in self.exclude:
if pattern in filename:
return False
if self.include:
return any(p in filename for p in self.include)
return True
def trace(self, frame, event, arg):
filename = frame.f_code.co_filename
if not self.should_trace(filename):
return None # Don't trace this frame
if event == 'line':
print(f"{os.path.basename(filename)}:{frame.f_lineno}")
return self.trace
tracer = FilteredTracer(include_patterns=['myapp/'])
sys.settrace(tracer.trace)Execution Time per Line
import sys
import time
class TimingTracer:
def __init__(self):
self.times = {}
self.last_time = None
self.last_key = None
def trace(self, frame, event, arg):
now = time.perf_counter()
if event == 'line':
if self.last_key:
elapsed = now - self.last_time
self.times[self.last_key] = self.times.get(self.last_key, 0) + elapsed
filename = frame.f_code.co_filename
lineno = frame.f_lineno
self.last_key = (filename, lineno)
self.last_time = now
return self.trace
def report(self):
for (filename, lineno), time_spent in sorted(
self.times.items(),
key=lambda x: -x[1]
)[:10]:
print(f"{time_spent*1000:.2f}ms - {filename}:{lineno}")
tracer = TimingTracer()
sys.settrace(tracer.trace)
# Run code
sys.settrace(None)
tracer.report()CLI Options
# Count only
python -m trace --count script.py
# Trace execution
python -m trace --trace script.py
# List functions called
python -m trace --listfuncs script.py
# Track caller relationships
python -m trace --trackcalls script.py
# Generate report
python -m trace --count --report script.py
# Ignore directories
python -m trace --ignore-dir=/usr/lib --trace script.pyWhen to Use trace
| Use Case | Tool |
|---|---|
| Line-by-line debugging | trace --trace |
| Code coverage | trace --count --report |
| Call graph analysis | trace --trackcalls |
| Performance hotspots | Custom timing tracer |
| Understanding code flow | sys.settrace() |
For production coverage, use coverage.py. The trace module is best for debugging and understanding program flow.
React to this post: