2.1 Debugging in Python with Visual Studio Code#
Motivation#
Debugging is not merely about fixing errors; it’s about understanding the behavior of your code under various conditions, ensuring reliability, and improving performance. Effective debugging can significantly reduce development time, enhance code quality, and create more robust applications.
Imagine you’re working on a Python project that processes data from an online store. Your task is to write a script that calculates the total sales for each product category. However, after trying to run your script, you notice that is throwing an error. You’ve checked your code multiple times, but the mistake remains elusive.
Create a new file named sales.py
, add the following code and try to understand how it works:
def calculate_total_sales(sales_data):
total_sales = 0
for category, sales in sales_data.items():
total_sales += sales
return total_sales
sales_data = {"electronics": 10000, "books": 5000, "clothing": "7000", "toys": 3000}
total_sales = calculate_total_sales(sales_data)
print(f"Total sales: ${total_sales}")
Setting Up and Running the Debugger#
Open your Python script in VS Code and make sure you have the Python extension installed. To install it, you can use the command:
code --install-extension ms-python.python
.Open the Debug view by clicking on the Run and Debug view icon on the left sidebar or pressing
Ctrl+Shift+D
. Select a Python interpreter if prompted. SelectPython Debugger
and selectPython File
.Start debugging by selecting the appropriate Python file configuration and pressing the green play button or pressing
F5
. Make sure the “Raised Exceptions” option is ticked in the Breakpoints panel.
Observing the Exception and Inspecting Variables#
As the debugger runs, it will halt execution when it encounters the uncaught TypeError exception due to attempting to add an integer to a string. This halting point allows us to inspect the program’s state just before the exception occurred.
Inspect the
sales_data
variable in the Variables panel to notice that the value for “clothing” is a string,"7000"
, instead of an integer.
Setting a Breakpoint and Watching Variables#
With the knowledge that our program halts due to a TypeError, we’ll focus on the calculation of total_sales
:
Add a breakpoint at the
total_sales += sales
line.Add
total_sales
andsales
to the Watch panel to monitor their values in real-time.Restart the debugger and observe as it stops at our breakpoint before the exception occurs.
Step through the for loop using the “Step Over” button (or pressing
F10
) to iterate through each category and sale.
Identifying and testing a solution for the Issue#
As you step through each iteration, when reaching “clothing”, you’ll notice in the Watch panel that sales
holds the string value "7000"
. This is the moment of realization that the data type inconsistency is causing our issue.
Modify the
sales_data
directly in the watch panel with a secondary click and selecting “Change Value” to correct the value for “clothing” from"7000"
to7000
(an integer).Continue execution by pressing the “Continue” button (or pressing
F5
) in the debugger.Observe that the debugger now completes the execution without halting on an exception, and the correct total sales value is printed to the console.
Other Types of Breakpoints#
In our first example, we used an uncaught exception breakpoint to halt the execution of our program. Let’s now use our example to explore other types of breakpoints.
Conditional Breakpoints#
Conditional Breakpoints pause execution when a specified condition is true.
Open your code in Visual Studio Code and navigate to the
for
loop line.Right-click on the left margin and select “Add Conditional Breakpoint.”
Enter
sales == 5000
as the condition. This setup ensures the debugger halts for the “books” category, allowing inspection of variables at that point.
Hit Count Breakpoints#
Hit Count Breakpoints trigger after the breakpoint has been hit a specified number of times.
On the same line as before, add a breakpoint.
Right-click on the breakpoint and select “Edit Breakpoint” -> “Hit Count.”
Specify the hit count as
3
. The debugger will now pause execution the third time it reaches this line, useful for examining the accumulatedtotal_sales
after several iterations.
Logpoints#
Logpoints are used to log messages to the debug console without stopping the program.
Right-click on the left margin next to the loop and select “Add Logpoint.”
Type
Processing {category}: {sales}
in the message field. This action allows real-time monitoring of which items are being processed and their respective sales without halting the program.
Logpoints are an alternative to the common practice of using print statements for debugging.
Function Breakpoints#
Function breakpoints halt execution when entering a specified function.
In the Debug view under Breakpoints, click on “Add Function Breakpoint” and type the name of the function,
calculate_total_sales
.When you run the debugger, it will pause every time
calculate_total_sales
is invoked, allowing you to inspect the initial state of the function and its parameters.
Handling Exceptions#
In our first example, we shown how the debugger can halt execution when an exception is raised. Let’s now explore how to configure the debugger to pause execution only when a not handled exception is raised.
To see this in action let’s replace our calculate_total_sales
, with the following code:
def calculate_total_sales(sales_data):
total_sales = 0
for category, sales in sales_data.items():
try:
total_sales += sales
except TypeError:
print(f"Invalid sales data for {category}: {sales}")
return total_sales
In this modified version, we’ve added a try-except block to handle the TypeError exception. We’ll now configure the debugger to pause only when an exception is raised and not handled. To do this, we need to ensure that only the “Uncaught Exceptions” option is checked in the Breakpoints panel.
Now, if we run the debugger, since the exception is handled, the debugger will not pause when the TypeError exception is raised. However, if we remove the try-except block, the debugger will pause when the exception is raised.
Test your knowledge
Clone the debugging exercise with the command
git clone git@gitlab.com:msdp.book/debbuging-exercise-1.git
Find the bug.
Function Breakpoints and Debugging Inside a Third-Party Library#
Suppose we are working on a Python project that uses pandas for data manipulation and we have the following code:
import pandas as pd
s = pd.Series([1, 2, 3, 4])
s.add_suffix('_item')
We want to understand how the add_suffix
method works internally, in particular, we would like to investigate an issue that occurs with _get_axis_name
which is another method that is used by add_suffix
(see source code here). To do this, we’ll configure our debugger to step into the pandas library code and break when it reaches the _get_axis_name
function.
Step 1: Configuring the Debugger#
Open VS Code and navigate to your Python script.
Open the
launch.json
file in the.vscode
folder. If it doesn’t exist, create it by going to the Run and Debug view, selecting “create a launch.json file”, and then choosing a Python configuration.Modify the
launch.json
file to include thejustMyCode
option set tofalse
. This tells the debugger to include third-party library code in the debugging session.
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Current File",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": false
}
]
}
Step 2: Setting a Function Breakpoint#
Go to the Run and Debug view in VS Code.
Open the Breakpoints pane and find the option to add a function breakpoint. Click on “Add Function Breakpoint”.
Type the name of the function you want to break on. In this case, enter
_get_axis_name
.Start the debugger by selecting the appropriate configuration for the current file and pressing the green play button or pressing
F5
.
What to Expect#
When you run the debugger with these settings and execute your script, the debugger will pause execution when it reaches the _get_axis_name
function call within the pandas library. This allows you to:
Step through the pandas library code to see how the
_get_axis_name
function is implemented.Inspect variables and understand the internal workings of the function.
Gain insights into the behavior of third-party libraries, which can be invaluable for debugging complex issues or enhancing your understanding of these libraries.
Keep in mind
Stepping into third-party library code can sometimes be overwhelming due to the complexity and volume of the code. However, it can also be a powerful way to learn more about these libraries and to troubleshoot issues more effectively or even adapt them to your specific needs.
References#
https://code.visualstudio.com/docs/python/python-quick-start https://code.visualstudio.com/docs/python/debugging https://code.visualstudio.com/docs/editor/debugging