Python Trace Tables — Compucademy

Compucademy
5 min readDec 9, 2020

--

The ability to trace the values of variables during program execution is a great aid in ensuring your code is doing what it is supposed to do and in avoiding logic errors — those pesky bugs which don’t crash your program but give you unexpected results or even slip by you unnoticed only to come back and bite you later on.

One way to keep track of values as a program executes is by using a trace table. The ability to create these is very helpful in the real-world development of software solutions. While is is certainly worth being able to trace an algorithm manually, on paper or a whiteboard, is is also very useful to be able to do this programmatically, so you can see the values of your variables at every step in a program’s execution for a given input.

There are several ways to achieve this with Python. We will look at two in this article.

Tracing Python Variables Using the sys Module

Here’s a very basic example showing how to trace the values of variables in a function call using the Python sys module.

A couple of things to note are:

  • If you want to trace code which is not in a function, you will need to “cheat” by putting it inside a function such as main() as in the example below. This is because the trace function given works by inspecting function call frames.
  • When you have called the function you wish to trace, you need to add sys.settrace(None) of you will get a whole lot of extra output which will likely not make much sense.
import sysdef trace(frame, event, arg_unused):
print((event, frame.f_lineno, frame.f_locals))
return trace
def main():
x = 10
y = 20
sys.settrace(trace)
main()
sys.settrace(None)

Output:

('call', 9, {})
('line', 10, {})
('line', 11, {'x': 10})
('return', 11, {'x': 10, 'y': 20})
>>>

So what is going on here?

Well, we have told Python to use our user-defined function trace() to produce a trace of any function calls we make. So when well call main() the trace is created and output. If you look closely at the output for the given function, you can see a line-by-line listing of the event, the lineno (line number), and the f_locals - i.e. the local variables for the function currently being executed.

Pretty cool huh?

Tracing the Fibonacci Function in Python

Let’s now look at a more complex example.

import sysdef trace(frame, event, arg_unused):
print((event, frame.f_lineno, frame.f_locals))
return trace
sys.settrace(trace)def fibonacci_iterative(n):
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
fibonacci_iterative(4)
sys.settrace(None)

Output:

('call', 12, {'n': 4})
('line', 13, {'n': 4})
('line', 14, {'n': 4, 'a': 0, 'b': 1})
('line', 15, {'n': 4, 'a': 0, 'b': 1, 'i': 0})
('line', 14, {'n': 4, 'a': 1, 'b': 1, 'i': 0})
('line', 15, {'n': 4, 'a': 1, 'b': 1, 'i': 1})
('line', 14, {'n': 4, 'a': 1, 'b': 2, 'i': 1})
('line', 15, {'n': 4, 'a': 1, 'b': 2, 'i': 2})
('line', 14, {'n': 4, 'a': 2, 'b': 3, 'i': 2})
('line', 15, {'n': 4, 'a': 2, 'b': 3, 'i': 3})
('line', 14, {'n': 4, 'a': 3, 'b': 5, 'i': 3})
('line', 16, {'n': 4, 'a': 3, 'b': 5, 'i': 3})
('return', 16, {'n': 4, 'a': 3, 'b': 5, 'i': 3})
>>>

Tracing Recursive Function Calls in Python

Another situation where you might want to trace the execution of a program is to better understand what happens when a recursive function is called. You can read more about recursion here.

This can be achieved using the above method, as for example in the following example:

import sysdef trace(frame, event, arg_unused):
print((event, frame.f_lineno, frame.f_locals))
return trace
sys.settrace(trace)def fibonacci_recursive(n):
if n < 2:
return n
return fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2)
fibonacci_recursive(4)
sys.settrace(None)

Output:

('call', 12, {'n': 4})
('line', 13, {'n': 4})
('line', 15, {'n': 4})
('call', 12, {'n': 3})
('line', 13, {'n': 3})
('line', 15, {'n': 3})
('call', 12, {'n': 2})
('line', 13, {'n': 2})
('line', 15, {'n': 2})
('call', 12, {'n': 1})
('line', 13, {'n': 1})
('line', 14, {'n': 1})
('return', 14, {'n': 1})
('call', 12, {'n': 0})
('line', 13, {'n': 0})
('line', 14, {'n': 0})
('return', 14, {'n': 0})
('return', 15, {'n': 2})
('call', 12, {'n': 1})
('line', 13, {'n': 1})
('line', 14, {'n': 1})
('return', 14, {'n': 1})
('return', 15, {'n': 3})
('call', 12, {'n': 2})
('line', 13, {'n': 2})
('line', 15, {'n': 2})
('call', 12, {'n': 1})
('line', 13, {'n': 1})
('line', 14, {'n': 1})
('return', 14, {'n': 1})
('call', 12, {'n': 0})
('line', 13, {'n': 0})
('line', 14, {'n': 0})
('return', 14, {'n': 0})
('return', 15, {'n': 2})
('return', 15, {'n': 4})
>>>

This is fine up to a point, but there is a way to get a clearer trace for recursive algorithms. To use it, create a file with the following code in the same folder as where the recursive code you want to trace is:

# trace_recursion.pyfrom functools import wrapsdef trace(func):
# Store function name, for later use
func_name = func.__name__
separator = '| ' # Used in trace display
# Set the current recursion depth
trace.recursion_depth = 0
@wraps(func)
def traced_func(*args, **kwargs):
# Display function call details
print(f'{separator * trace.recursion_depth}|-- {func_name}({", ".join(map(str, args))})')
# Begin recursing
trace.recursion_depth += 1
result = func(*args, **kwargs)
# Exit current level
trace.recursion_depth -= 1
# Display return value
print(f'{separator * (trace.recursion_depth + 1)}|-- return {result}')
return resultreturn traced_func

Then you can use the trace helper function to get a nice, easy to read representation of the recursive calls, their arguments and their return values.

For example:

from trace_recursion import tracedef factorial(n):
if n <= 1:
# Base case
return 1
else:
# Recursive case
return n * factorial(n - 1)
factorial = trace(factorial)
factorial(5)

to get the super-handy output:

|-- factorial(5)
| |-- factorial(4)
| | |-- factorial(3)
| | | |-- factorial(2)
| | | | |-- factorial(1)
| | | | | |-- return 1
| | | | |-- return 2
| | | |-- return 6
| | |-- return 24
| |-- return 120
>>>

This article has shown you two ways you can get Python to give you valuable information about the your programs by tracing their execution. Try these methods out for yourself with your own programs if you need to develop your understanding of a particular algorithm or to debug a logic error.

Happy computing!

Originally published at https://compucademy.net on December 9, 2020.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Compucademy
Compucademy

Written by Compucademy

Computer Science Education for the Next Generation. Teaching, Training and Resources.

No responses yet

Write a response