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 counts

Coverage 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.py

Custom 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.py

When to Use trace

Use CaseTool
Line-by-line debuggingtrace --trace
Code coveragetrace --count --report
Call graph analysistrace --trackcalls
Performance hotspotsCustom timing tracer
Understanding code flowsys.settrace()

For production coverage, use coverage.py. The trace module is best for debugging and understanding program flow.

React to this post: