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
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) == 120Save 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:
- Step Into to observe an additional frame added to the call stack.
- Pause when the condition
n == 2is met, and verify the current frame against the caller's framen. - Step Out to observe the current call returning.
- Do not call
factorialagain in the Watch, as this would create new recursive execution. - Finally, run both assertions without the debugger.
Troubleshooting Order
- Does the issue still reproduce under normal operation?
- Are the correct files, test processes, or service processes started?
- Are breakpoints bound to executable lines?
- Are the interpreter, virtual environment, container, and source code mapping consistent?
- Has the condition never been true or failed during evaluation?
- 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.