Skip to content

4.6. Debugging

What is software debugging?

Software debugging is the systematic process of finding and fixing errors, or "bugs," in your code. It involves using specialized tools to run a program in a controlled manner, allowing you to pause execution, inspect the state of your application (like variable values), and trace the flow of logic to pinpoint the root cause of a problem.

Why is debugging essential for MLOps?

In MLOps, where data and code are tightly coupled, debugging is critical for ensuring reliability and correctness. Key benefits include:

  1. Ensuring Model Correctness: Debugging helps verify that data preprocessing, feature engineering, and model inference logic behave exactly as intended.
  2. Improving Code Quality: It uncovers subtle issues in your pipelines and APIs that might otherwise lead to silent failures or incorrect predictions in production.
  3. Boosting Efficiency: Interactive debugging is far more efficient than littering your code with print() statements. It provides a structured way to analyze program state without constant code modification and re-execution.

What is a breakpoint?

A breakpoint is a signal that tells the debugger to pause your program's execution at a specific line of code. When a breakpoint is hit, the program stops, and you can inspect the call stack, local variables, and other runtime information to understand what the program is doing at that exact moment. This allows you to verify your assumptions and identify where the actual behavior deviates from the expected behavior.

How does debugging compare to logging?

Debugging and logging are complementary practices, not competing ones.

  • Logging is about recording events as your application runs, creating a historical record of its behavior. It's invaluable for monitoring applications in production and understanding issues that occur over time.
  • Debugging is an interactive, real-time process used during development. It allows you to actively investigate an issue by pausing the code and exploring its state.

A common workflow is to use logs to identify that a problem exists and to narrow down its location, then use the debugger to perform a deep-dive investigation and find the root cause.

What tools are available for debugging Python?

Python's standard library includes the Python Debugger (pdb), a powerful but text-based tool that runs in the command line.

However, for a more intuitive and productive experience, modern Integrated Development Environments (IDEs) are recommended. Visual Studio Code provides a best-in-class graphical debugger that is seamlessly integrated into the editor, making it easy to visualize the debugging process.

How do you configure the VS Code debugger for a Python project?

VS Code manages debugging configurations in a launch.json file located in the .vscode directory of your project.

  1. Open the Run and Debug View: Click the "Run and Debug" icon in the Activity Bar (or press Ctrl+Shift+D).
  2. Create a Configuration: If you don't have a launch.json file, VS Code will prompt you to "create a launch.json file." Click it and select "Python File" from the dropdown.
  3. Define the Configuration: This creates a launch.json file with a default configuration to run the currently open Python file. For MLOps projects, you'll often want to configure it to run a specific script, module, or test suite.

Here is an example configuration for running a Python module:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Python: Run Module",
      "type": "python",
      "request": "launch",
      "module": "your_package.your_module",
      "justMyCode": true
    }
  ]
}

How do you use the VS Code debugger?

Once configured, the debugging workflow is straightforward:

  1. Set Breakpoints: In your editor, click in the gutter to the left of a line number. A red dot will appear, marking the breakpoint.
  2. Start Debugging: Select your desired configuration from the dropdown in the "Run and Debug" view and click the green "Start Debugging" arrow (or press F5).
  3. Control Execution: When a breakpoint is hit, a toolbar appears with controls to:
    • Continue (F5): Resume execution until the next breakpoint.
    • Step Over (F10): Execute the current line and move to the next one.
    • Step Into (F11): If the current line contains a function call, move into that function's code.
    • Step Out (Shift+F11): Finish executing the current function and return to the line where it was called.
  4. Inspect State: While paused, you can inspect variable values by hovering over them in the editor or using the "Variables" and "Watch" panels in the sidebar. The "Debug Console" allows you to execute arbitrary code in the current context.

What are some advanced debugging techniques?

To become more efficient, master these powerful features:

  • Conditional Breakpoints: Right-click a breakpoint to add a condition. The debugger will only pause if the condition evaluates to True. This is invaluable for debugging loops or events that occur frequently.
  • Logpoints: Instead of pausing, a logpoint prints a message to the Debug Console and continues execution. It's like a print() statement you can add or remove without modifying your code.
  • Watch Panel: Add variables or expressions to the "Watch" panel to monitor their values as you step through the code. This helps track how state changes over time.
  • Call Stack: The "Call Stack" panel shows the sequence of function calls that led to the current location. This is essential for understanding the execution path and how you got into a particular state.

Additional Resources