Skip to content

VOL 00 / CH 04 / LESSON 04

4.4 Debugger Integration Exercise

Prerequisites: Pages 1-3 of this chapter

Estimated time: 20 minutes

Exercise environment: Python 3 with a configured debugger

You are ready to continue when: you can explain recursion using stack frames and validate with automated tests

This page has a boundary bug to fix and a correct recursive function for observing the stack. Run each normally and record whether it passes or fails before entering the debugger. After a change, verify again in a fresh process.

Exercise 1: Boundary Conditions

Copy the material screening program from the base page. First, use python3 material_checker.py to confirm the assertion fails. Then, set a breakpoint at the if condition inside the loop. Observe the zero-weight items, fix > to >=, and then rerun the script after exiting the debug mode.

Exercise 2: Recursive Call Stack

python
def factorial(n):
    if isinstance(n, bool) or not isinstance(n, int):
        raise TypeError("n must be an integer")
    if n < 0:
        raise ValueError("n must be non-negative")
    if n == 0:
        return 1
    return n * factorial(n - 1)

assert factorial(0) == 1
assert factorial(5) == 120

Save this as factorial_debug.py. Running python3 factorial_debug.py should exit successfully without output. Booleans are explicitly rejected because Python's bool is a subclass of int, while this example accepts integer inputs only. Recursion is also bounded by the interpreter's stack limit; this exercise is not suitable for arbitrarily large factorials.

When pausing in the recursive line:

  1. Step Into to observe an additional frame added to the call stack.
  2. Pause when the condition n == 2 is met, and verify the current frame against the caller's frame n.
  3. Step Out to observe the current call returning.
  4. Do not call factorial again in the Watch, as this would create new recursive execution.
  5. Finally, run both assertions without the debugger.

Troubleshooting Order

  1. Does the issue still reproduce under normal operation?
  2. Are the correct files, test processes, or service processes started?
  3. Are breakpoints bound to executable lines?
  4. Are the interpreter, virtual environment, container, and source code mapping consistent?
  5. Has the condition never been true or failed during evaluation?
  6. Have optimizations, asynchronous, or thread switching altered the pause location?

Log breakpoints, conditional breakpoints, and the debug console can all introduce overhead or side effects. Do not rely on a single observation from a debugging environment to represent a repeatable test. The assert in this section is only for keeping the example minimal; real projects should place these boundaries within a testing framework and be aware that Python removes assert in -O mode.

Acceptance

  • Can distinguish between Continue, Step Over, Step Into, and Step Out.
  • Can trace up the call stack to find the caller and explain where the parameters come from.
  • Knows that Watch expressions may execute code.
  • After fixing, can pass assertions in a new process.

Keep the before-and-after results for the boundary fix, and be ready to explain n in each recursive frame. The next chapter covers project dependencies, version selection, and reproducible builds.

Next: Why Package Managers Are Necessary

Built with VitePress | Software Systems Atlas