trace — Follow Program Flow (2025)

Purpose:Monitor which statements and functions are executed as aprogram runs to produce coverage and call-graph information.

The trace module is useful for understanding the way a programruns. It watches the statements executed, produces coverage reports,and helps investigate the relationships between functions that calleach other.

Example Program

This program will be used in the examples in the rest of the section.It imports another module called recurse and then runs a functionfrom it.

trace_example/main.py

from recurse import recursedef main(): print('This is the main program.') recurse(2)if __name__ == '__main__': main()

The recurse() function invokes itself until the level argumentreaches 0.

trace_example/recurse.py

def recurse(level): print('recurse({})'.format(level)) if level: recurse(level - 1)def not_called(): print('This function is never called.')

Tracing Execution

It is easy use trace directly from the command line. Thestatements being executed as the program runs are printed when the--trace option is given. This example also ignores the location ofthe Python standard library to avoid tracing into importlib andother modules that might be more interesting in another example, butthat clutter up the output in this simple example.

$ python3 -m trace --ignore-dir=.../lib/python3.7 \--trace trace_example/main.py --- modulename: main, funcname: <module>main.py(7): """main.py(10): from recurse import recurse --- modulename: recurse, funcname: <module>recurse.py(7): """recurse.py(11): def recurse(level):recurse.py(17): def not_called():main.py(13): def main():main.py(18): if __name__ == '__main__':main.py(19): main() --- modulename: main, funcname: mainmain.py(14): print('This is the main program.')This is the main program.main.py(15): recurse(2) --- modulename: recurse, funcname: recurserecurse.py(12): print('recurse({})'.format(level))recurse(2)recurse.py(13): if level:recurse.py(14): recurse(level - 1) --- modulename: recurse, funcname: recurserecurse.py(12): print('recurse({})'.format(level))recurse(1)recurse.py(13): if level:recurse.py(14): recurse(level - 1) --- modulename: recurse, funcname: recurserecurse.py(12): print('recurse({})'.format(level))recurse(0)recurse.py(13): if level:

The first part of the output shows the setup operations performed bytrace. The rest of the output shows the entry into eachfunction, including the module where the function is located, and thenthe lines of the source file as they are executed. recurse()is entered three times, as expected based on the way it is called inmain().

Code Coverage

Running trace from the command line with the --countoption will produce code coverage report information, detailing whichlines are run and which are skipped. Since a complex program isusually made up of multiple files, a separate coverage report isproduced for each. By default the coverage report files are writtento the same directory as the module, named after the module but with a.cover extension instead of .py.

$ python3 -m trace --count trace_example/main.pyThis is the main program.recurse(2)recurse(1)recurse(0)

Two output files are produced, trace_example/main.cover:

trace_example/main.cover

 1: from recurse import recurse 1: def main(): 1: print('This is the main program.') 1: recurse(2) 1: if __name__ == '__main__': 1: main()

and trace_example/recurse.cover:

trace_example/recurse.cover

 1: def recurse(level): 3: print('recurse({})'.format(level)) 3: if level: 2: recurse(level - 1) 1: def not_called(): print('This function is never called.')

Note

Although the line def recurse(level): has a count of 1,that does not mean the function was only run once. It means thefunction definition was only executed once. The same applies todef not_called():, because the function definition isevaluated even though the function itself is never called.

It is also possible to run the program several times, perhaps withdifferent options, to save the coverage data and produce a combinedreport. The first time trace is run with an output file, itreports an error when it tries to load any existing data to merge withthe new results before creating the file.

$ python3 -m trace --coverdir coverdir1 --count \--file coverdir1/coverage_report.dat trace_example/main.pyThis is the main program.recurse(2)recurse(1)recurse(0)Skipping counts file 'coverdir1/coverage_report.dat': [Errno 2]No such file or directory: 'coverdir1/coverage_report.dat'$ python3 -m trace --coverdir coverdir1 --count \--file coverdir1/coverage_report.dat trace_example/main.pyThis is the main program.recurse(2)recurse(1)recurse(0)$ python3 -m trace --coverdir coverdir1 --count \--file coverdir1/coverage_report.dat trace_example/main.pyThis is the main program.recurse(2)recurse(1)recurse(0)$ ls coverdir1coverage_report.datmain.coverrecurse.cover

To produce reports once the coverage information is recorded to the.cover files, use the --report option.

$ python3 -m trace --coverdir coverdir1 --report --summary \--missing --file coverdir1/coverage_report.dat \trace_example/main.pylines cov% module (path) 7 100% trace_example.main (trace_example/main.py) 7 85% trace_example.recurse(trace_example/recurse.py)

Since the program ran three times, the coverage report shows valuesthree times higher than the first report. The --summaryoption adds the percent covered information to the output. Therecurse module is only 87% covered. Looking at the cover file forrecurse shows that the body of not_called is indeed neverrun, indicated by the >>>>>> prefix.

coverdir1/trace_example.recurse.cover

 3: def recurse(level): 9: print('recurse({})'.format(level)) 9: if level: 6: recurse(level - 1) 3: def not_called():>>>>>> print('This function is never called.')

Calling Relationships

In addition to coverage information, trace will collect andreport on the relationships between functions that call each other.

For a simple list of the functions called, use --listfuncs.

$ python3 -m trace --listfuncs trace_example/main.py | \grep -v importlibThis is the main program.recurse(2)recurse(1)recurse(0)functions called:filename: trace_example/main.py, modulename: main, funcname:<module>filename: trace_example/main.py, modulename: main, funcname:mainfilename: trace_example/recurse.py, modulename: recurse,funcname: <module>filename: trace_example/recurse.py, modulename: recurse,funcname: recurse

For more details about who is doing the calling, use --trackcalls.

$ python3 -m trace --listfuncs --trackcalls \trace_example/main.py | grep -v importlibThis is the main program.recurse(2)recurse(1)recurse(0)calling relationships:*** .../lib/python3.7/trace.py *** --> trace_example/main.py trace.Trace.runctx -> main.<module> --> trace_example/recurse.py*** trace_example/main.py *** main.<module> -> main.main --> trace_example/recurse.py main.main -> recurse.recurse*** trace_example/recurse.py *** recurse.recurse -> recurse.recurse

Note

Neither --listfuncs nor --trackcalls honors the--ignore-dirs or --ignore-mods arguments, so part of theoutput from this example is stripped using grep instead.

Programming Interface

For more control over the trace interface, it can beinvoked from within a program using a Trace object.Trace supports setting up fixtures and other dependenciesbefore running a single function or executing a Python command to betraced.

trace_run.py

import tracefrom trace_example.recurse import recursetracer = trace.Trace(count=False, trace=True)tracer.run('recurse(2)')

Since the example only traces into the recurse() function, noinformation from main.py is included in the output.

$ python3 trace_run.py --- modulename: trace_run, funcname: <module><string>(1): --- modulename: recurse, funcname: recurserecurse.py(12): print('recurse({})'.format(level))recurse(2)recurse.py(13): if level:recurse.py(14): recurse(level - 1) --- modulename: recurse, funcname: recurserecurse.py(12): print('recurse({})'.format(level))recurse(1)recurse.py(13): if level:recurse.py(14): recurse(level - 1) --- modulename: recurse, funcname: recurserecurse.py(12): print('recurse({})'.format(level))recurse(0)recurse.py(13): if level:

That same output can be produced with the runfunc() method,too.

trace_runfunc.py

import tracefrom trace_example.recurse import recursetracer = trace.Trace(count=False, trace=True)tracer.runfunc(recurse, 2)

runfunc() accepts arbitrary positional and keyword arguments,which are passed to the function when it is called by the tracer.

$ python3 trace_runfunc.py --- modulename: recurse, funcname: recurserecurse.py(12): print('recurse({})'.format(level))recurse(2)recurse.py(13): if level:recurse.py(14): recurse(level - 1) --- modulename: recurse, funcname: recurserecurse.py(12): print('recurse({})'.format(level))recurse(1)recurse.py(13): if level:recurse.py(14): recurse(level - 1) --- modulename: recurse, funcname: recurserecurse.py(12): print('recurse({})'.format(level))recurse(0)recurse.py(13): if level:

Saving Result Data

Counts and coverage information can be recorded as well, just as withthe command line interface. The data must be saved explicitly, usingthe CoverageResults instance from the Trace object.

trace_CoverageResults.py

import tracefrom trace_example.recurse import recursetracer = trace.Trace(count=True, trace=False)tracer.runfunc(recurse, 2)results = tracer.results()results.write_results(coverdir='coverdir2')

This example saves the coverage results to the directorycoverdir2.

$ python3 trace_CoverageResults.pyrecurse(2)recurse(1)recurse(0)$ find coverdir2coverdir2coverdir2/trace_example.recurse.cover

The output file contains

 #!/usr/bin/env python # encoding: utf-8 # # Copyright (c) 2008 Doug Hellmann All rights reserved. # """>>>>>> """ #end_pymotw_header >>>>>> def recurse(level): 3: print('recurse({})'.format(level)) 3: if level: 2: recurse(level - 1) >>>>>> def not_called():>>>>>> print('This function is never called.')

To save the counts data for generating reports, use the infile andoutfile arguments to Trace.

trace_report.py

import tracefrom trace_example.recurse import recursetracer = trace.Trace(count=True, trace=False, outfile='trace_report.dat')tracer.runfunc(recurse, 2)report_tracer = trace.Trace(count=False, trace=False, infile='trace_report.dat')results = tracer.results()results.write_results(summary=True, coverdir='/tmp')

Pass a filename to infile to read previously stored data, and afilename to outfile to write new results after tracing. If infileand outfile are the same, it has the effect of updating the filewith cumulative data.

$ python3 trace_report.pyrecurse(2)recurse(1)recurse(0)lines cov% module (path) 7 42% trace_example.recurse(.../trace_example/recurse.py)

Options

The constructor for Trace takes several optional parametersto control runtime behavior.

count
Boolean. Turns on line number counting. Defaults to True.
countfuncs
Boolean. Turns on list of functions called during the run.Defaults to False.
countcallers
Boolean. Turns on tracking for callers and callees. Defaults toFalse.
ignoremods
Sequence. List of modules or packages to ignore when trackingcoverage. Defaults to an empty tuple.
ignoredirs
Sequence. List of directories containing modules or packages to beignored. Defaults to an empty tuple.
infile
Name of the file containing cached count values. Defaults to None.
outfile
Name of the file to use for storing cached count files. Defaults toNone, and data is not stored.

See also

trace — Follow Program Flow (2025)
Top Articles
Latest Posts
Recommended Articles
Article information

Author: Clemencia Bogisich Ret

Last Updated:

Views: 5665

Rating: 5 / 5 (60 voted)

Reviews: 91% of readers found this page helpful

Author information

Name: Clemencia Bogisich Ret

Birthday: 2001-07-17

Address: Suite 794 53887 Geri Spring, West Cristentown, KY 54855

Phone: +5934435460663

Job: Central Hospitality Director

Hobby: Yoga, Electronics, Rafting, Lockpicking, Inline skating, Puzzles, scrapbook

Introduction: My name is Clemencia Bogisich Ret, I am a super, outstanding, graceful, friendly, vast, comfortable, agreeable person who loves writing and wants to share my knowledge and understanding with you.