Python Debugging Techniques

Python Debugging Techniques

posted 12 min read

While developing a software application in one phase, you need to ensure that it is flawless and error-free. Therefore, to check that the functionality works as expected, we debug the codebase. In SDLC, debugging occurs after successful completion of the software testing phase. In addition, the errors found during the testing phase were fixed by debugging the code. Hence, successful debugging means the complete uncovering of errors and the fixation of bugs, which makes the application robust and efficient. That is why debugging is important in SDLC.

Overview of Common Types of Bugs in Python Programs

There are various types of bugs in the Python program that can be fixed by debugging the code. Common types of bugs you may encounter are syntax, runtime, logical, name, type, index, and filenotfound errors, where each error indicates a different type of problem in the code but can be resolved or fixed by code debugging.

Therefore, in this article, I will cover the details of Python-oriented debugging, with information on debugging tools and the process of debugging your code using different techniques and strategies. Let’s get started.

Understanding Python's Debugging Tools

To debug code effectively, understanding Python debugging tools and setting up the environment is crucial. Here, I will discuss three different tools that can help you debug your Python code efficiently.

1. Introduction to Python's Built-in Debugging Tools

The pdb module is a Python built-in debugger that is part of the Python standard library. It provides a step-by-step method for debugging the code by importing the library. This is a powerful module that allows the Python developer to pause the execution of the code at a breakpoint. Breakpoints are the setup of specific points and then evaluate the values of variables and logic of the conditions or expression. Furthermore, it has a very straightforward way of debugging, which is invoked using the command line interface. It is a perfect tool for simple tasks and for those debuggers who are comfortable working with cmd.

import pdb #import
def checker(a, b):
    pdb.set_trace() #debugging starts here
    if b > a:
        print("B is greater than A")
    elif b == a:
        print("B is equal to A")
    else:
        print("A is smaller than B")
a = 33
b = 200
result = checker(a,b)

2. Exploring Third-Party Debugging Tools and IDE Integrations

The second popular method for debugging Python code is to set up an integrated debugging environment (IDE). These debugging IDEs will ensure an effective way of writing, running, and debugging the code. Furthermore, these IDS provide you with booting features of breakpoints, variable inspection, execution workflow (with step-into, step-out, and step-over), watch expression, stack trace, and advanced code editing features that make debugging much easier. Several debugging IDEs are PyCharm, Spider, Visual Studio Code with Python extension, PyDev (plugin for Eclipse), JupyterLab, Wing IDE, etc., and some third-party tools, including PySnooper and pdb++.

Note: Breakpoints are the markers set by a programmer to temporarily stop the execution for debugging purposes.
Variable inspection is checking the current value of a variable while a program is paused.
A stack trace is a report of the active stack frames at a certain point in time during program execution, typically captured when an exception occurs.

3. Configuring Debugging Environments for Efficient Debugging Workflows

  1. Use Integrated Development Environments (IDEs): IDEs like PyCharm, Visual Studio Code, or Jupyter Notebook offer debugging tools that can streamline the debugging process.
  2. Set Breakpoints: Place breakpoints in your code to pause execution at specific points and inspect variables, helping to identify the issues.
  3. Step Through Code: Utilize features like stepping into, over, or out of functions to understand code execution flow better.
  4. Inspect Variables: Utilize IDE features to examine variable values in real-time, aiding in the detection of unexpected behavior.
  5. Use Debugging Libraries: Libraries like pdb or ipdb provide additional debugging functionalities to enhance your debugging workflow.
  6. Logging: Incorporate logging statements in your code to track program flow and variable values, providing insights into the program's behavior.
  7. Unit Tests: Write unit tests to catch bugs early in the development phase, making debugging more efficient.

Using Print Statements for Debugging

1. Leveraging Print Statements for Basic Debugging

Debuggers are the preferred way of debugging your programs, but if you don't have access or, in some cases, your program is of a smaller level, then leveraging the print() function for debugging is an efficient way. You can tackle many bugs with just the print statement at the right place where you think an exception or abnormal behavior might occur. This is one of the simplest and most basic ways to debug your program before setting up the entire debugging environment on your machine. Therefore, the print() function will help you debug your code and fix bugs at an early stage, which is the priority of developers.

2. Tips for Effective Use of Print() to Trace Program Flow

Print statements are versatile tool for every programming language. It not only helps you print your code output but is also used for debugging. In Python, there are some tips for the effective use of the print statement that help you trace the program flow:

  1. Use the print() function to display the output result, message, and outcome of conditional and logical statements and other logging information of code that helps you understand the program flow.
  2. For effective debugging, strategically place the print() function at the right place in your code where an unexpected behavior might occur to trace the execution of the program. Then, evaluate the output to identify where the code is breaking.
  3. Utilized print() functions in conditional statements to verify whether the logic of your program generates correct outcomes or not.

3. Printing Variable Values and Debugging Output Formatting

Suppose we have a function that calculates the average of a list of integers. However, the output is not what we expected, therefore we need to debug the code using the print statements. This is how we can use the print statement:

def average(nums): #function for calculating the average of a list
    sum = 0
    for list in nums:
        sum += list
    print(f"Sum of List", sum) #to check that the sum is not zero 
    print(f"Length of List", len(nums)) # to check that the list size is not zero
    return sum / len(nums)

number = [5,3,8,9,2,7]
#the format for print the output for effective readability
print(f"The Average of {number} is:", average(number)) 

In this example, we have a function called average() that takes the list as a parameter and then returns the average. We created a list to test the function. Next, we use the print statement at specific points, displaying the values of the sum alongside the length of the list. This allows us to evaluate the output and quickly verify the point that deviates from the program's normal flow. In addition, f-strings are used in the program for effective readability and to follow the debugging output formatting.

Tip: Always start debugging small sections of your code once at a time to isolate issues and make troubleshooting more manageable.

Debugging with Logging

1. Introduction to Python's Logging Module

For small programs, the print() function is completely fine but cannot be the best practice, when dealing with large Python programs that have complicated logic. Therefore, you should have a persistent log that stores all the data of your program such as information, warnings, exceptions, and errors. Python has introduced a built-in module for logging. You need to import the library into your program, and no further installation is required.

2. Configuring Logging Levels and Formatting for Debugging

In Python, the logging module consists of 5 different levels. Each level defines the category of the log message based on the type of severity you encountered during the program execution. These Five levels are as follows:

  1. Debug: Used in the program development phase to diagnose code issues.
  2. Info: Used to provide a successful program case in which there is no error in the program.
  3. Warning: Used to indicate the warning at runtime that might occur in the future. e.g. removal of module older version.
  4. Error: Used to print all errors and bugs encountered during program execution and cause the program to deviate from its normal flow.
  5. Critical: A situation or an error in the program that violates the requirements and crashes the application suddenly.

Enough for logging levels? Now, understand the debugging format when you use the logging module. First, we import the logging library.

import logging #import logging library

Then, set the logging level to debug, as we are using it for debugging purposes. Here, the setting level refers to the level at which we want to start logging. So, write debug to log all the info and start from there.

logging.basicConfig(level=logging.DEBUG, format = '%(name)s - %(levelname)s - %(message)s') # create and config the logger  

Then, you can use all the levels for printing the actual messages like this.

logging.debug("A Debug Logging Message")  
logging.warning("A Warning Logging Message")  
logging.error("An Error Logging Message")  

That is how you set up the log messages at the debug level, and I hope you understand the basic formatting of debugging using logging.

3. Using Logging to Trace Program Execution and Capture Errors

Here, we will understand how to trace the program execution and capture the error message using the logging module with the help of a coding example. Suppose we have a program with a division function, but encounter an error of zero division exception. For this, we need to debug the code to understand the complete log of the program execution including the timestamp and the line of code where the error occurs using the logging module instead of the print statement.

import logging #import logging library
#configure logging with debug mode
logging.basicConfig(filename = 'file.log', filemode='w', level=logging.DEBUG, format = '%(name)s - %(asctime)s - %(levelname)s - %(message)s') # create and config the loger 

#function that can contain an error
def division(num1, num2):
    try:
        num3 = num1 / num2
    except ZeroDivisionError as e:
        logging.error("Error Occur %s", e)
    else:
        logging.debug("Result is %s", num3)

#program flow
logging.info("Info Message: Program Started")
division(8,2)
division(8,0) 
logging.info("Execution Ends ") 

Note: Understand the error message carefully because it will guide you to the root cause of the error occurrence.

In the output, it clearly shows that the first function call gives the correct output, but when the second time we call the same function with different values the error occurs, and all the log info of the program execution is stored in the file. So, this is how you can trace your program execution and configure all the relevant logging information for debugging and analysis.

Interactive Debugging with Debuggers

1. Using Interactive Debuggers to Inspect Code Execution

Now, let's move towards interactive debuggers, by which you can debug your code and find the result upon each execution. Here, I use VS code with the Python extension for interactive debugging. In addition, I will discuss a step-by-step guide to debug the simple Python code for a better understanding of VS code.

1-1. Open Interactive Debugger

Begin by opening VS code on your computer, then move toward your Python code directory. After that, open your code file. The content inside my code file is:

even_numbers = [2,4,6,8,10] #list of even number
square = (num ** 2 for num in even_numbers) #generator expression in single line
for numbers in square:
    print(numbers)

1-2. Setting Breakpoints

In your Python code, set the breakpoint by clicking on the left margin of the line numbers from where you want debugging to start. Here, I am setting the three breakpoints.

Caution: Be cautious while using breakpoints because sometimes using too many breakpoints or placing them at the wrong location can cause the debugging process confusing.

1-3. Start Debugging Process

Then, click on the "Run and Debug" icon in the sidebar, and choose the appropriate Python interpreter for debugging. This will start the debugging process and will pause the execution at every breakpoint you set.

1-4. Stepping Through Code

You have four different debugging controls, that will appear when the debugging process starts. These are step-over, step-into, continue, and pause. You can use any one of them, for executing the current line and shifting towards the next, you have to click on step over control. To step into control, you can move into the function you called on the current line. Whereas pause and continue are straightforward, so there is no need to explain.

2. Examining Variable Values and Stack Traces During Debugging Sessions

You can also examine the variable values and stack traces in the debug sidebar while debugging your Python code. This will show you all the variables in the current scope with their values. Whereas the stack traces will show the chain of the function calls and lead you to the root that causes the error.

FAQs
Q: Why is debugging necessary?
A: It helps you to find errors or bugs at an early stage and makes the testing phase easier.
Q: How can I start debugging?
A: After writing the code, you can use the print() function or logging or can set the breakpoints for interactive debugging.

Wrapping Up

Finally, we discussed that debugging is an important part of the software development phase. Mostly, developers who work on small-scale projects just ignore debugging and shift toward fixing the bugs without finding which part caused the error. Therefore, adding debugging to your programming part, and also exploring new debugging tools can enhance your debugging process by effectively reducing time consumption. We learned different strategies that include simple print() statements to logging modules, but you can incorporate a debugger as it is the preferred choice of many developers and industries. I hope you like this guide. If you have any questions, then feel free to give feedback. Thank you for following this guide. Happy Coding!

If you read this far, tweet to the author to show them you care. Tweet a Thanks

More Posts

Basic operators in Python

Ferdy - May 19

Automating With Python: A Developer’s Guide

Tejas Vaij - May 14

Mastering Lambda Functions in Python: A comprehensive Guide

Abdul Daim - May 6

Leveraging Python for System Administration

Tejas Vaij - May 1

Learn how to build a user-friendly, conversational Telegram bot with python

Astra Bertelli - Apr 30
chevron_left