Skip to content

2 Debugging

Dimitris Tsapetis edited this page Nov 10, 2022 · 2 revisions

For a video presentation of the following content, please follow this link

Debugging

Debugging is the software development process that allows developers to identify their logical mistakes and pinpoint the conditions under which the it occurs. This allows them to correct their approach on the issue or in more complicated cases find a workaround to resolve them.

Two-tools are most commonly used for discovering logical errors that occur during the development process. The first standalone tool is logging. Either as independent print statements to the console, or the inherent logging framework existing in Python, offer a quick overview of code execution flow. On the other hand, debugging tools incorporated in an Integrated Development Environment (IDE), such as PyCharm offer a vast variety of tools to delve into details of even the most intricate bugs. These two types of tools (logging/debuggers) are not mutually exclusive. On the contrary, the compliment each other and as the developer experience grows so grows the ability to identify the fastest / most efficient problem solving method.

For the scope of this document, we will go through the the detailed debugging options of a few widely used IDEs such as PyCharm, Visual Studio Code and an extension for the popular jupyter notebooks, that allow for a similar debugging experience to advanced IDEs.

Yet, before we delve into the details of debugging it is imperative to go through a set of common Python 3 errors and they way we address them.

Common Python3 Errors

Common Python 3 Error Messages (and How to Eliminate Them!)

MIT Course Notes

Syntax Errors

The syntax of Python language consists of a set of rules that defines the position and sequence a set of commands should appear.

Most common cases of syntax errors:

  • Forgetting parentheses (e.g. print statement)
  • Forgetting a colon (:) at the end of if or for loops.
  • Leaving out a keyword
  • Incorrect indentation
  • Empty block

These errors can be easily detected by most IDEs. In some cases, the error codes provided by python can be misleading though, pointing to a wrong source of the problem or displaying many more syntax problems that actually exist. In that case, the developer should tackle the syntax error in the sequence the appear on the code. This strategy usually addresses issues below our initial ones.

Runtime Errors

Even though a program can be syntactically correct, i.e. having no syntax errors, the code is still prone to errors. These errors occur due to unexpected or unpredicted values of the variables or branches the flow of our program might take. Usually we refer to these errors as crashes!

Some Runtime error examples include:

  • Division by zero
  • Trying to perform an operation between incompatible types
  • Accessing a element of a data structure that does not exist
  • Accessing a file that does not exist.

To address these issues the developers must follow a more detailed debugging process that will allow them to understand the workflow of the code and under which circumstances the errors occur. To address these issues and edge cases, code checks should be performed and the code should be able to deal with them using exception handling.

Logical Errors

This type of errors are usually the most difficult to detect and fix. Here, the code executes successfully without errors, but produces wrong results. The most common case is that the programmer has minor mistakes that severely affect the execution flow. Another example is having major duplicates blocks of code with minor differences. Especially during copy pasting code, usually is the most common time these errors occur.

Some common examples of logical errors are the following:

  • using wrong variable names
  • Integer division instead of floating point division and vice versa
  • Wrong operator precedence
  • Excessive code copy-pasting

The most helpful but labor intensive way to discover and address these issues is by creating unit tests that verify not only the execution but also the correct output of your code.


Debugging basics (PyCharm)

Step 2. Debug your first Python application | PyCharm

In this section the debugging tools of PyCharm IDE will be examined.

Breakpoints

Breakpoints | PyCharm

Breakpoints, are points of intentional interruption of code flow for debugging purposes. PyCharm allows for several types of breakpoints, but the two most common ones will be explained below.

Line Breakpoints

To add a breakpoint, click a line of code right next to the numbering. Once the code execution reaches this point, code execution will be suspended and further debugging option become available.

This is the simplest for suspending code execution, that always pauses the workflow. More advanced breakpoints are available in PyCharm that stop the execution only when certain conditions are met. These are called conditional breakpoints. The developer can add a logical condition to the breakpoint by right clicking it.

Here any logical condition can be provided at the condition entry. The code execution will only be suspended only at this line of code is reached and at the same time the required condition is met.

Conditional Breakpoints

Exception Breakpoints

PyCharm is able to suspend the execution of a program an Exception is thrown. They can applied globally when an exception-based code crash occurs. In order to enable them, perform the following steps:

  • Set a breakpoint and start a debug configuration.

    • Click at the view breakpoint option.
  • In the Breakpoints dialog that pops-up select the Python Exception Breakpoint option.

Debug tool window

When starting a debug session, the Debug Tool window appears. This window contains a lot of capabilities, to display and analyze the data while debugging. Many tools appearing here, provide detailed information about the current debug session, the sequence of method calls in the program workflow, as well as the current values of our variables.

Sessions

Multiple files and configuration can be debugged at the same time. The various runs can be found at the top of the debug windows, as separate tabs. Each tab contains a whole new set of tools, independent from each other that allow tracing of errors and exploring variable for multiple cases. When closing one of the tabs, at the same time the respective debug session terminates.

Frames

The frames window contains the call stack of our current debug session. Each time the code enters a new method, the method is appended to the stack. As a result, the top function in the stack is always the current point that our code execution has stopped. By double clicking one of the other methods names, the developer observes at the same time a change of the variables. This action switches the context to the respective method and displays all the variables at the moment of entry. As a result, all the steps until the current one can be monitored at the same time.

Variables

This tool contains a list of all variables currently used in the selected frame. Understanding the values of the variables, and their evolution during the course of different commands is considered fundamental for our understanding of the code. Selecting a different frame form the Frames windows will also the variables relevant to that scope.

Various details are show for each one of the variables, such as their type, values and nested properties. For advanced data types such as arrays, dictionaries the developer can retrieve key-value pairs, length etc. In the case of objects, their attributes can be observed. In all these cases, right clicking any variable reveals a number of options such as:

  • Setting Value
  • Copying value
  • Inspecting variable

Watches

A watch can be either a variable or an expression, that we want to keep track of during the current scope of our program. This is especially useful when the developer wants to keep track of the result of a variable or a complex expression, that is not readily accessible. The watches option is only available after suspension of a code after hitting a breakpoint.

Debugging Console

When a debugging session starts, automatically PyCharm launches the debugging console. This is an interactive environment, with access to the variables of the current frame. The developer can display variables, type command and execute them, for performing quick checks on the available data.

Stepping

Last but not least is the capability of stepping, which translates to step-by-step execution of the code. Five different stepping options are available.

  • Step Over: This options takes you to the next line after current highlighted one. Even if the current line contains method calls, they will be evaluated and the execution will move to the next line. Only if breakpoints exist inside these method calls, the debugger will stop at them.
  • Step Into: This option steps into a method, so that the developer can investigate what occurs in more detail. If several method exist in the current line, then PyCharm will prompt the user to choose which one of the methods to step in to.
  • Step into my code: In a similar fashion to the step into command, here PyCharm will disregard any method belonging to external libraries and will step in to the method created by the developer.
  • Step out: This options instructs the debugger to complete the execution of the current method and step right outside the current function.
  • Run to cursor: This is the final stepping option. By clicking a line the cursor will be positioned on it and the options is enabled. This will continue the execution of our code until the selected line is reaching allowing the user to skip multiple code lines without needing for an additional breakpoint.

Jupyter Notebook

Although Jupyter notebooks is considered the go to tool for rapid prototyping, its capabilities for debugging compared to other IDEs is considered limited. Print statements and displaying variables to the console was the main tool at hand to understand faults in one’s code.

Jupyter Lab which has become a mainstream way to manage multiple Jupyter notebooks, is making progress towards merging the gap with IDEs, by proving enriched UI, file browser text editors and consoles.

In this direction, a debugging tool is developed for Jupyter notebooks, that for can only be used as an extension but will be included by default in future Jupyter Lab releases.

This debugger contains several debugging options that would be expected from a full-scale IDE such as:

  • Variable Explorer
  • Call Stack
  • Source preview
  • Ability to set breakpoints next to lines

More information on this extension can be found in the link below.

Debugger - JupyterLab 3.4.8 documentation