Table of Contents
- 2.1 Before debugging with pdb
- 2.2 Start a debug session
- 2.3 Variable inspection commands
- 2.4 Execution control commands
- 2.5 Breakpoint management
- 2.6 Conditional Breakpoints
- 2.7 Evaluate Python expressions in pdb
- 2.8 Module Summary
- 3.1 Source code navigation commands
- 3.2 Understanding and navigating the stack trace
- 3.3 Source code display commands
- 3.4 Third-party tools: Pylint
- 3.5 Third-party tools: ipdb
- 3.6 Other useful utilities
- 3.7 Module Summary
- 4.1 Configuring VS Code for Python
- 4.2 Python extension for VS Code
- 4.3 The Run and Debug panel
- 4.4 Breakpoint management in VS Code
- 4.5 Variable and Call Stack Inspection
- 4.6 Watch Expressions and Debug Console
- 4.7 Module Summary
- 5.1 Logpoints
- 5.2 Launch Configurations and launch.json
- 5.3 Debug a web application (FastAPI)
- 5.4 Debugging in a Docker container
- 5.5 Remote debugging via SSH
- 5.6 Debugging Jupyter Notebooks
- 5.7 Useful VS Code Extensions
- 5.8 Module Summary
- 6.1 PyCharm Editions
- 6.2 Basic debugging features in PyCharm
- 6.3 Inspect complex data structures
- 6.4 Remote debugging in PyCharm via SSH
- 6.5 PyCharm Plugins
- 6.6 Module and course summary
1. Course Overview
This course, titled Debugging in Python 3, is taught by Douglas Starnes, independent entrepreneur, author and speaker. The main goal is to familiarize yourself with three distinct tools for debugging Python applications, tools that won’t guarantee you error-free code the first time, but will help you get closer to it faster and more efficiently.
Topics covered in this course
The main themes covered are as follows:
- pdb: Python’s built-in debugger, used at the command line. It works on the same principle as Git — you interact with it by typing commands.
- Visual Studio Code with Python extension: a graphical interface for debugging Python applications, offering functionality impossible or very difficult to achieve with
pdb. - PyCharm: A full-featured Python IDE from JetBrains, popular for debugging complex data structures and connecting to remote projects.
- Add-on Utilities: Third-party tools like Pylint, ipdb, Black and isort that help reduce debugging time or improve code quality before even running the application.
Prerequisites
Before starting this course, you should be comfortable with:
- The basics of Python 3 (syntax, functions, classes, modules)
- The fundamental debugging workflow (understanding what a breakpoint, stack trace, etc. is)
What you will be able to do after this course
At the end of this course, you will master three different tools for debugging Python code, and you will be able to choose the most suitable according to your context (command line, IDE, remote environment, notebook, etc.).
2. Debugging at the command line with pdb
Module duration: 25m 43s
2.1 Before debugging with pdb
What is pdb?
pdb (Python Debugger) is a command-line debugger included in the Python standard library. This is a huge advantage: you don’t have to install anything, pdb is available wherever Python is installed. As it is a module, you must import it before using it.
Interacting with pdb is done in the same way as Git commands: you type textual commands to control the debugging session. At first glance this may seem cumbersome, but pdb offers exactly the same functionality as graphical debuggers in other IDEs.
Start debugger: pdb.set_trace()
The typical way to start pdb is to import the module and then call the set_trace() function at the place in the code where you want to start debugging:
import pdb; pdb.set_trace()
We often see the import and the call to set_trace() on the same line. This is perfectly legal in Python — multiple statements can be separated by a semicolon. PEP-8 discourages this practice in general, but for debugging it is commonly accepted because debug code is temporary anyway.
Here’s how this fits into a source file:
def get_investment_info(investment):
import pdb; pdb.set_trace()
price = get_current_price(investment["coin"])
value = investment["quantity"] * price
return value
The breakpoint() function (Python 3.7+)
Since Python 3.7, a new built-in function has been introduced:
def get_investment_info(investment):
breakpoint()
price = get_current_price(investment["coin"])
value = investment["quantity"] * price
return value
breakpoint() is equivalent to import pdb; pdb.set_trace() — it starts the debugger without requiring an explicit import. It seems cleaner, but it gives a false impression: it suggests that a breakpoint is placed there, which is not the case. For this reason, and because it is only available from Python 3.7, the course prefers to use set_trace().
Launch application with debugger
No special interpreter options are required. You simply run your file normally:
python app.py
When the interpreter reaches the call to set_trace(), pdb takes control and prints something like:
> /home/user/projet/app.py(35)get_investment_info()
-> price = get_current_price(investment["coin"])
(Pdb)
- The first line indicates the file path, line number, and function in which execution is suspended.
- The second line (preceded by
->) shows the code that will be executed in the next step. (Pdb)is the prompt that waits for your commands.
2.2 Start a debug session
The demo application
The application used in the demos is deliberately simple: a cryptocurrency portfolio manager. The entry point creates a portfolio (dictionary list), loops through it and displays information about each investment. Prices are determined by the get_price() function:
# app.py
def get_current_price(coin):
print(f"Getting price for {coin}")
if coin == "bitcoin":
return get_bitcoin_price()
elif coin == "ethereum":
return get_ethereum_price()
elif coin == "solana":
return get_solana_price()
def get_investment_info(investment):
import pdb; pdb.set_trace()
price = get_current_price(investment["coin"])
total = investment["quantity"] * price
print(f"{investment['coin']}: {investment['quantity']} @ ${price} = ${total:.2f}")
def main():
portfolio = [
{"coin": "bitcoin", "quantity": 0.5},
{"coin": "ethereum", "quantity": 5},
{"coin": "solana", "quantity": 50},
]
for investment in portfolio:
get_investment_info(investment)
if __name__ == "__main__":
main()
2.3 Variable inspection commands
Once pdb has taken control, you can inspect the variable values with the following commands:
| Order | Aliases | Description |
|---|---|---|
p <expr> | — | Shows the value of the expression |
pp <expr> | — | Displays the value in pretty-print format (readable) |
whatis <expr> | — | Shows the variable type |
Usage examples
(Pdb) p investment
{'coin': 'bitcoin', 'quantity': 0.5}
(Pdb) pp investment
{'coin': 'bitcoin', 'quantity': 0.5}
(Pdb) whatis investment
<class 'dict'>
(Pdb) p investment["coin"]
'bitcoin'
(Pdb) p investment["quantity"]
0.5
For simple structures, p and pp give the same result. For complex nested structures (list dictionaries, objects, etc.), pp produces much more readable formatting.
2.4 Execution control commands
These commands allow you to navigate the code during a pdb session:
| Order | Aliases | Description |
|---|---|---|
next | n | Execute the current line and move on to the next one (without entering the called functions) |
step | s | Executes the current line and enters the function if it is a call |
continue | c | Resumes execution until next breakpoint |
return | r | Continue until the return of the current function |
quit | q | Exits the debugger and ends execution |
Difference between next and step
next(n): equivalent of “Step Over” in graphics IDEs. If the current line contains a function call,pdbexecutes this call in full and moves to the next line.step(s): equivalent of “Step Into”. If the current line is a function call,pdbenters this function and positions itself on its first line.
(Pdb) n
> /home/user/projet/app.py(36)get_investment_info()
-> total = investment["quantity"] * price
(Pdb) s
--Call--
> /home/user/projet/app.py(5)get_current_price()
-> def get_current_price(coin):
2.5 Breakpoint management
The break command (aka b)
The break command has several functions depending on the arguments provided:
| Use | Description |
|---|---|
break | List all active breakpoints |
break <line_number> | Place a breakpoint on this line in the current file |
break <function_name> | Places a breakpoint at the entry of a function |
break <file>:<line> | Place a breakpoint in another file |
break <line>, <condition> | Sets a conditional breakpoint (see next section) |
Examples
(Pdb) break 30
Breakpoint 1 at /home/user/projet/app.py:30
(Pdb) break
Num Type Disp Enb Where
1 breakpoint keep yes at /home/user/projet/app.py:30
(Pdb) break get_current_price
Breakpoint 2 at /home/user/projet/app.py:5
In the list of breakpoints, the columns indicate:
- Num: the breakpoint identifier
- Type:
breakpoint(permanent) ortbreak(temporary, deleted after the first pass) - Available:
keep(permanent) ordel(temporary) - Enb:
yes(enabled) orno(disabled) - Where: the file and the line
The clear command (aka cl)
The clear command deletes breakpoints:
| Use | Description |
|---|---|
clear | Remove all breakpoints (requires confirmation) |
clear <number> | Delete the breakpoint with this identifier |
clear <line> | Remove the breakpoint on this line |
(Pdb) clear 1
Deleted breakpoint 1 at /home/user/projet/app.py:30
(Pdb) clear
Clear all breaks? yes
Enable and disable breakpoints
Rather than deleting a breakpoint, you can temporarily disable it with disable and re-enable it with enable:
(Pdb) disable 2
Disabled breakpoint 2 at /home/user/projet/app.py:5
(Pdb) enable 2
Enabled breakpoint 2 at /home/user/projet/app.py:5
When a breakpoint is deactivated, execution no longer stops there but it remains in the list — you do not have to recreate it.
Temporary breakpoints: tbreak
The tbreak command creates a breakpoint that automatically deletes itself after being reached once:
(Pdb) tbreak 20
Breakpoint 3 at /home/user/projet/app.py:20
(Pdb) c
> /home/user/projet/app.py(20)get_current_price()
-> return 45000.0
Deleted breakpoint 3
2.6 Conditional breakpoints
A conditional breakpoint only suspends execution if a Python condition evaluates to True. This is very useful in loops or functions called many times: you only interrupt execution when a particular condition is met.
(Pdb) break 20, investment["coin"] == "ethereum"
Breakpoint 1 at /home/user/projet/app.py:20
(Pdb) break
Num Type Disp Enb Where
1 breakpoint keep yes at /home/user/projet/app.py:20
stop only if investment["coin"] == "ethereum"
With this conditional breakpoint, execution only pauses when investment["coin"] is "ethereum". For "bitcoin" and "solana", the breakpoint is ignored.
You can also add a condition to an existing breakpoint with the condition command:
(Pdb) condition 1 investment["coin"] == "ethereum"
New condition set for breakpoint 1.
2.7 Evaluate Python expressions in pdb
The ! command (or simply type a Python expression) allows you to evaluate arbitrary Python code in the current application context. You can thus modify the state of the application during debugging:
(Pdb) !investment["coin"] = "solana"
(Pdb) p investment
{'coin': 'solana', 'quantity': 0.5}
This functionality is extremely powerful: it allows you to test corrections without restarting the application, to modify values to simulate edge cases, or to test complex expressions in a real context.
(Pdb) !price = 99999.99
(Pdb) p price
99999.99
Warning: Changes to application state during debugging are real — they affect program execution.
2.8 Module summary
In this module, you learned to:
- Prepare a Python application for debugging with
pdbby adding a call topdb.set_trace()or usingbreakpoint()(Python 3.7+) - Start the Python interpreter normally to start a debugging session
- Inspect simple and complex variables with
p,ppandwhatiscommands - Control execution with
next,step,continue,returnandquit - Manage breakpoints: set them, list them, delete them, activate/deactivate them, and create temporary ones
- Create conditional breakpoints to fine-tune debug control
- Evaluate Python expressions to change the state of the running application
3. Advanced debugging with pdb and its allies
Module duration: 26m 3s
3.1 Source code navigation commands
In this module, you will delve deeper into the pdb commands to navigate the code more precisely, in particular by entering nested functions and managing the stack trace.
The step command (aka s) — Step Into
The step command executes the current line. If this line contains a function call, pdb enters this function and positions itself on its first line, whether there is a breakpoint or not. This is the equivalent of “Step Into” in graphics debuggers.
(Pdb) s
--Call--
> /home/user/projet/app.py(5)get_current_price()
-> def get_current_price(coin):
The up (aka u) command — Step Out
When you have entered a function with step, you can move up one level in the stack trace with up. This is similar to “Step Out”, but with an important nuance: the function frame is not deleted — you simply move the cursor up in the stack trace. You can go back down if necessary.
(Pdb) up
> /home/user/projet/app.py(35)get_investment_info()
-> price = get_current_price(investment["coin"])
The down command (aka d) — Go back down the stack
Symmetrical with up, the down command goes down one level in the stack trace towards more recent frames.
(Pdb) down
> /home/user/projet/app.py(5)get_current_price()
-> def get_current_price(coin):
Navigate multiple levels at once
up and down accept a numeric argument to move multiple levels in a single command:
(Pdb) up 2 # remonte de 2 niveaux
(Pdb) down 3 # descend de 3 niveaux
3.2 Understanding and navigating the stack trace
The where command (aka w, bt)
The where command displays the complete stack trace:
(Pdb) where
/home/user/projet/app.py(47)<module>()
-> get_investment_info(investment)
/home/user/projet/app.py(35)get_investment_info()
-> price = get_current_price(investment["coin"])
/home/user/projet/app.py(5)get_current_price()
-> if coin == "bitcoin":
> /home/user/projet/app.py(10)get_bitcoin_price()
-> return 45000.0
Reading the stack trace:
- Frames are displayed oldest to newest (top to bottom)
- Most recent (bottom) frame is usually the current frame
- The chevron
>indicates the current frame (the one you are in) - The thin arrow
->indicates the line on which this frame is suspended
In the example above, there are 4 frames:
- The main module (
<module>) hanging on line 47 get_investment_infohanging on line 35get_current_pricehanging on line 5get_bitcoin_price(current frame>) hanging on line 10
After an up, the chevron moves to the middle frame:
(Pdb) up
> /home/user/projet/app.py(5)get_current_price()
-> if coin == "bitcoin":
(Pdb) where
/home/user/projet/app.py(47)<module>()
-> get_investment_info(investment)
/home/user/projet/app.py(35)get_investment_info()
-> price = get_current_price(investment["coin"])
> /home/user/projet/app.py(5)get_current_price()
-> if coin == "bitcoin":
/home/user/projet/app.py(10)get_bitcoin_price()
-> return 45000.0
Note that the same 4 frames are present because get_bitcoin_price has not returned yet. When a function returns, its frame is removed from the stack trace.
3.3 Commands for displaying source code
The list command (aka l)
The list command displays the source code around the current line:
(Pdb) l
8 def get_bitcoin_price():
9 return 45000.0
10 -> return 45000.0
11
12 def get_ethereum_price():
13 return 3000.0
By default, list displays 11 lines centered on the current line. You can pass arguments:
(Pdb) l 1, 20 # affiche les lignes 1 à 20
(Pdb) l . # affiche la ligne courante et ses environs
The longlist command (aka ll)
longlist displays the entire current function (or module if you are at module level):
(Pdb) ll
5 def get_current_price(coin):
6 print(f"Getting price for {coin}")
7 if coin == "bitcoin":
8 return get_bitcoin_price()
9 elif coin == "ethereum":
10 return get_ethereum_price()
11 elif coin == "solana":
12 -> return get_solana_price()
3.4 Third-party tools: Pylint
Presentation of Pylint
Pylint is a static Python code analyzer. It analyzes your code without executing it and identifies:
- Code errors (unused imports, undefined variables, etc.)
- Convention issues (non-compliance with PEP-8, incorrect naming, etc.)
- Refactoring opportunities (redundant code, unnecessary return in
elif, etc.) - Warnings (variable redefinition, bad practices, etc.)
In addition to analysis, Pylint can generate class diagrams and offers an optional spell checker. Plugins are available for popular frameworks like Django and Pydantic.
Installation and use
pip install pylint
pylint app.py
Pylint output example
************* Module app
app.py:1:0: C0304: Final newline missing (missing-final-newline)
app.py:8:4: C0103: Function name "get_Bitcoin_price" doesn't conform to snake_case naming style (invalid-name)
app.py:18:4: R1705: Unnecessary "elif" after "return", remove the leading "el" from "elif" (no-else-return)
app.py:30:4: W0621: Redefining name 'investment' from outer scope (line 45) (redefined-outer-name)
app.py:35:4: W0104: Statement seems to have no effect (pointless-statement)
-----------------------------------
Your code has been rated at 5.94/10
Message prefixes indicate their category:
- C (Convention): style issues (PEP-8, naming, etc.)
- R (Refactor): code that can be simplified
- W (Warning): warnings about potential bugs
- E (Error): probable errors
- F (Fatal): errors preventing Pylint from analyzing the file further
Step-by-step correction with Pylint
C0304 — Final newline missing: Pylint recommends that the last line of a file be a blank line. Just add a blank line at the end of the file.
C0103 — Invalid function name: The function get_Bitcoin_price should be renamed to get_bitcoin_price (snake_case). Consider updating all places where it is called.
R1705 — Unnecessary elif after return: The following code contains unnecessary elifs:
# Avant (avec elif inutiles)
def get_current_price(coin):
if coin == "bitcoin":
return get_bitcoin_price()
elif coin == "ethereum":
return get_ethereum_price()
elif coin == "solana":
return get_solana_price()
else:
return 0.0
# Après (sans elif inutiles, plus propre)
def get_current_price(coin):
if coin == "bitcoin":
return get_bitcoin_price()
if coin == "ethereum":
return get_ethereum_price()
if coin == "solana":
return get_solana_price()
return 0.0
W0621 — Redefined outer name: If a local variable in a function has the same name as a variable in the outer scope, Pylint reports this. The solution is to rename the local variable:
# Avant
def get_investment_info(investment): # paramètre "investment"
for investment in portfolio: # réutilise "investment" — confus!
...
# Après
def get_investment_info(investment):
for inv in portfolio: # nom différent
...
After these corrections, the Pylint rating can go from 5.94 to 7+ out of 10.
3.5 Third-party tools: ipdb
Presentation of IPython and ipdb
IPython is an improved Python interpreter that adds features like code completion, automatic indentation, syntax highlighting, and better presentation of results. This is the basis of the Jupyter Notebook project.
ipdb brings all IPython improvements to the pdb debugger. The command interface remains the same — you use the same commands (n, s, c, b, etc.) — but the experience is enhanced by syntax highlighting and code completion.
Installation and use
pip install ipdb
To use ipdb instead of pdb, simply override the import:
# Remplacer :
import pdb; pdb.set_trace()
# Par :
import ipdb; ipdb.set_trace()
Advantages of ipdb over pdb
- Syntactic coloring: the displayed code is colored, which improves readability
- Autocompletion: Tab-completion in debug session
- Better presentation of tracebacks: errors are easier to read
- Integration with IPython: access to IPython magic commands if necessary
Use combined with the IPython REPL
IPython offers an enriched REPL accessible from the terminal:
pip install ipython
ipython
In [1]: def add(a, b):
...: return a + b
...:
In [2]: add(1, 2)
Out[2]: 3
In [3]: import ipdb; ipdb.set_trace()
3.6 Other useful utilities
The course mentions several other tools that reduce the risk of bugs and debugging time:
| Tool | Role |
|---|---|
| Black | Automatic code formatter that enforces PEP-8 style rules |
| isort | Organizes and sorts imports while respecting conventions |
| mypy | Checks type annotations before execution (static type checking) |
These tools can be integrated into editors (VS Code, PyCharm) or into CI/CD pipelines.
pip install black isort mypy
# Formater un fichier avec Black :
black app.py
# Trier les imports avec isort :
isort app.py
# Vérifier les types avec mypy :
mypy app.py
3.7 Module Summary
In this module, you learned to:
- Enter functions with
stepand navigate the stack trace withupanddown - Show and read full stack trace with
where/w/bt - Show source code with
listandlonglist - Use Pylint to statically analyze code and improve its quality before even running it
- Use ipdb to get a rich command line debugging experience (syntax highlighting, completion)
- Know other useful tools: Black (formatting), isort (imports), mypy (type checking)
4. Debugging with Visual Studio Code
Module duration: 28m 42s
4.1 Configuring VS Code for Python
Visual Studio Code (VS Code) is a code editor developed by Microsoft. By default, its installation is minimalist — that’s intentional. Its strength lies in its extensibility through tens of thousands of extensions available in the VS Code Marketplace.
To debug Python applications in VS Code, you must install the Microsoft Python extension.
Install Python extension
Via the Extensions panel in VS Code:
- Click on the Extensions icon in the sidebar (or
Ctrl+Shift+X/Cmd+Shift+Xon macOS) - Filter by “Most Popular”
- Select the Python extension from Microsoft (the most popular at the time of training)
- Click on the blue button Install
Important: Do not install the pre-release version of the Python extension. It may contain bugs that would interfere with starting Python debugging in VS Code.
Installation takes a few seconds and does not require restarting VS Code.
4.2 The Python extension for VS Code
Once the extension is installed, VS Code gains several capabilities for Python:
- Run button: runs the Python file opened in the active tab
- Debug button: launch the Python file in the debugger
- No code addition in sources is necessary (unlike
pdb)
The Python extension therefore advantageously replaces import pdb; pdb.set_trace(). You work directly on your source code, without modifying it.
4.3 The Run and Debug panel
The Run and Debug panel (accessible via Ctrl+Shift+D / Cmd+Shift+D) is the control center for debugging in VS Code. It is divided into several sections:
| Section | Description |
|---|---|
| Variables | Shows all local and global variables in real time |
| Watch | List of Python expressions to monitor continuously |
| Call Stack | Equivalent of the stack trace of pdb |
| Breakpoints | List and management of all breakpoints set |
A major advantage over pdb: all of this information is visible simultaneously and variable values update in real time as you navigate through the code.
4.4 Managing breakpoints in VS Code
Set a breakpoint
Simply click in the gutter (the strip to the left of the line numbers) to place a breakpoint. A red circle appears. Clicking on this circle again removes it.
Alternatively, place the cursor on a line and press F9.
The Breakpoints section of the panel
The Breakpoints section lists all the breakpoints set. Each breakpoint is displayed with:
- A checkbox to enable/disable
- The file and line number
- The possible condition
Disable a breakpoint: uncheck the box. The red circle in the editor turns gray. The breakpoint remains set but the execution will no longer stop there.
Reactivate a breakpoint: recheck the box.
Delete a breakpoint: right click on the breakpoint in the gutter > “Remove Breakpoint”, or direct click on the red circle.
Conditional breakpoints in VS Code
To create a conditional breakpoint:
- Right click in the gutter next to the desired line
- Select “Add Conditional Breakpoint…”
- Enter the Python condition in the field that appears
- Validate with Enter
For example, to stop only when the variable coin is equal to "ethereum":
coin == "ethereum"
The advantage over pdb is that the GUI makes defining and managing conditions much simpler and more visual.
Debug exceptions
The Breakpoints section offers an additional option: Caught Exceptions and Uncaught Exceptions. By checking these options, VS Code automatically stops on lines that throw exceptions — even without an explicit breakpoint. This is a feature not possible with pdb without additional code.
4.5 Inspection of variables and the Call Stack
The Variables section
The Variables section automatically displays all variables in the current scope:
- Locals: local variables to the current function
- Globals: module global variables
You can expand complex objects (dictionaries, lists, class instances) by clicking on the arrow next to their name, without having to type a command. The values update in real time at each step.
The Call Stack section
The Call Stack is the graphical equivalent of the where command in pdb. It lists all active frames. By clicking on a frame in the Call Stack:
- Editor jumps to corresponding line in code
- The Variables section displays the variables in this specific frame
Compare with pdb: to do the same thing you had to use up/down then p/pp for each variable. With VS Code, it’s just a click away.
Example with multi-file application
For the advanced version of the application (divided into 4 files), the Call Stack allows you to easily navigate between the frames of different files:
# Fichiers de l'application refactorisée :
utilities.py # fonctions utilitaires (dates, prix)
investment.py # classe Investment
portfolio.py # classe Portfolio (gère les investissements)
app.py # point d'entrée
In the Call Stack, we can see:
summarize portfolio.py:30
main app.py:15
<module> app.py:18
Clicking on main in the Call Stack takes you directly to app.py:15 and shows the variables of main.
4.6 Watch Expressions and Debug Console
Watch Expressions
The Watch section allows you to define arbitrary Python expressions whose value is displayed and updated in real time during the entire debug session. This is a significant improvement over the p and pp commands in pdb.
To add a watch expression:
- Click on the
+in the Watch section - Enter the desired Python expression
# Exemples de watch expressions utiles :
investment["coin"]
len(portfolio)
investment["quantity"] * price
portfolio.total_value()
Watch expressions are reevaluated at each debug step and their value is updated automatically.
The Debug Console
The Debug Console is an interactive panel that works like the p and pp commands of pdb, but in a much more user-friendly way. You can type any Python expression there:
> investment
{'coin': 'bitcoin', 'quantity': 0.5}
> investment["quantity"] * 45000
22500.0
> portfolio
[{'coin': 'bitcoin', 'quantity': 0.5}, {'coin': 'ethereum', 'quantity': 5}, ...]
The Debug Console also supports auto-completion (Tab) and syntax highlighting. Unlike pdb commands, you see the formatted result directly in the panel.
4.7 Module summary
In this module, you have:
- Installed and configured the Python extension for Visual Studio Code
- Used the Debug button to start a debug session without modifying the source code
- Explored the Run and Debug panel: Variables, Watch, Call Stack, Breakpoints
- Managed breakpoints graphically: set them, activate/deactivate, condition
- Inspected variables in real time without typing commands
- Navigated the Call Stack with a single click
- Used Watch Expressions to monitor arbitrary expressions
- Used Debug Console for ad hoc inspections
5. Advanced Debugging with Visual Studio Code
Module duration: 26m 13s
This module has a strong demonstration focus. It covers advanced VS Code features for Python debugging.
5.1 Logpoints
What is a logpoint?
A logpoint is a tool halfway between the breakpoint and the watch expression. Unlike a breakpoint, it does not suspend execution of the application. It logs a message in the Debug Console each time the corresponding line is executed.
This is a great alternative to print() calls scattered throughout the code. Advantages over print():
- You do not need to modify the source code
- You don’t have to remove them before deploying
- Messages do not appear in application standard output
- You can enable/disable them like breakpoints
How to create a logpoint
- Right click in the gutter next to the desired line
- Select “Add Logpoint…”
- A red diamond (instead of the circle of a breakpoint) appears in the gutter
- Enter the message in the field that appears — no need for quotes
- To insert the value of a Python expression, surround it with braces
{}
# Exemple de message de logpoint :
Investment {investment['coin']}: quantity={investment['quantity']}, price={price}
The logpoint appears in the Breakpoints section of the Run and Debug panel, marked with the diamond symbol (♦).
Demonstration
In portfolio.py, function summarize(), we can place a logpoint on line 22. When debugging the application (by first opening app.py), the application runs without stopping, but the Debug Console displays:
Investment bitcoin: quantity=0.5, price=45000.0
Investment ethereum: quantity=5, price=3000.0
Investment solana: quantity=50, price=150.0
Investment cardano: quantity=100, price=2.5
5.2 Launch Configurations and launch.json
The multi-file problem
A drawback of VS Code is that the Debug button always launches the file currently open in the editor. If you have a multi-file application and you open a file that only contains a class definition, VS Code will “debug” that file, but since the code is legal with no side effects, nothing will happen.
The solution is to create a launch configuration.
Create launch.json file
- In the Run and Debug panel, click on the link “create a launch.json file”
- Select Python Debugger from the context menu
- Select Python File
VS Code automatically creates a .vscode/launch.json file:
{
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Current File",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal"
}
]
}
${file} means “the currently open file” — this does not fix the problem. Change the configuration to point to the entry point file:
{
"version": "0.2.0",
"configurations": [
{
"name": "Lancer l'application principale",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/refactored/app.py",
"console": "integratedTerminal"
}
]
}
With this configuration, no matter what file is open in the editor, VS Code will always start debugging from app.py.
Multiple configurations in launch.json
launch.json may contain multiple configurations. A drop-down menu at the top of the Run and Debug panel allows you to select the active configuration:
{
"version": "0.2.0",
"configurations": [
{
"name": "Lancer l'application principale",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/refactored/app.py",
"console": "integratedTerminal"
},
{
"name": "Attacher au serveur distant",
"type": "debugpy",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "/home/user/projet"
}
]
}
]
}
5.3 Debug a web application (FastAPI)
Demo App Overview
The web application used in the demos is a REST API built with FastAPI. It exposes three endpoints for managing a cryptocurrency portfolio:
# server.py
from fastapi import FastAPI
from sqlmodel import Session, select
from models import Investment, engine
import uvicorn
app = FastAPI()
@app.get("/investments")
async def get_investments() -> list[Investment]:
with Session(engine) as session:
investments = session.exec(select(Investment)).all()
return investments
@app.get("/investments/{coin}")
async def get_investment(coin: str) -> list[Investment]:
with Session(engine) as session:
investments = session.exec(
select(Investment).where(Investment.coin == coin)
).all()
return investments
@app.post("/investments")
async def new_investment(investment: Investment) -> Investment:
with Session(engine) as session:
session.add(investment)
session.commit()
session.refresh(investment)
return investment
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8001)
Launch.json configuration for FastAPI
VS Code offers predefined configurations for FastAPI and Django. When you create a launch.json and select “FastAPI”:
{
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: FastAPI",
"type": "debugpy",
"request": "launch",
"module": "uvicorn",
"args": [
"server:app",
"--host", "0.0.0.0",
"--port", "8001"
],
"jinja": true,
"console": "integratedTerminal"
}
]
}
Debugging workflow with FastAPI
- Set a breakpoint in an endpoint function (e.g., line 38 in
new_investment) - Select FastAPI configuration in the Run and Debug panel
- Click on Start Debugging
- Navigate to
http://localhost:8001/docsfor Swagger interface - Extend the POST method, click “Try it out”, fill the JSON and send
- VS Code suspends execution at breakpoint in
new_investment
5.4 Debugging in a Docker container
Workflow overview
Debugging in a Docker container follows a different workflow than previous demos. Rather than starting the application and the debugger simultaneously from VS Code, you:
- Start the Docker container normally
- Connect VS Code to the debugger afterward by “attaching” the session
Configure container with debugpy
debugpy is the implementation of the Debug Adapter Protocol (DAP) for Python — this is the protocol that VS Code uses to communicate with the Python debugger.
In the application source code (before the app code):
# server.py — ajout pour le débogage à distance
import debugpy
# Démarrer le serveur debugpy et écouter sur le port 5678
debugpy.listen(("0.0.0.0", 5678))
# Optionnel : attendre que VS Code se connecte avant de continuer
# debugpy.wait_for_client()
from fastapi import FastAPI
# ... reste du code
The Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8001
EXPOSE 5678
CMD ["python", "server.py"]
Build and launch container
# Construire l'image
docker build -t crypto-api .
# Lancer le conteneur en exposant les deux ports
docker run -p 8001:8001 -p 5678:5678 crypto-api
- Port
8001: client connection to REST API - Port
5678: connection from VS Code to thedebugpydebugger
Launch.json configuration for Docker (Remote Attach)
Add a configuration in launch.json by clicking “Add Configuration” and selecting “Python Debugger: Remote Attach”:
{
"name": "Python Debugger: Remote Attach",
"type": "debugpy",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "/app"
}
]
}
The pathMappings field is crucial: it allows VS Code to map local file paths to paths in the container.
Attach debugger
- Select the “Remote Attach” configuration in the Run and Debug panel
- Click on Start Debugging
- Set a breakpoint in
server.py - Send a request to the API (via curl, Postman or Swagger)
- VS Code suspends execution at breakpoint
5.5 Remote debugging via SSH
To debug an application running on a remote server (Linux), the workflow is similar to Docker debugging:
- On the remote server, add
debugpy.listen()in the code and launch the application - Create an SSH tunnel for port 5678:
ssh -L 5678:localhost:5678 user@remote-server - In VS Code, configure a “Remote Attach” launch configuration pointing to
localhost:5678 - Log in and debug normally
{
"name": "Attach SSH - Serveur distant",
"type": "debugpy",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "/home/vagrant/projet"
}
]
}
5.6 Debugging Jupyter Notebooks
This is functionality that pdb cannot offer. VS Code, with the right extensions, allows you to debug Jupyter Notebooks cells.
Necessary extensions
- Jupyter Extension Pack from Microsoft (from the Extensions panel)
- The ipykernel package installed in the Python environment
pip install ipykernel seaborn pandas jupyter
Create and configure a notebook in VS Code
- Open the Command Palette (
Ctrl+Shift+P/Cmd+Shift+P) - Type “Create new Jupyter notebook” and validate
- At the top right, click to select a kernel (Python environment with
ipykernel) - Save the notebook (essential step to activate debugging)
Example of notebook with bug
# Cellule 1 : Imports
import seaborn as sns
import pandas as pd
# Cellule 2 : Charger les données
tips = sns.load_dataset("tips")
# Cellule 3 : Taux de change
exchange_rates = {
"AUD": 1.52, # Dollar australien
"CAD": 1.35, # Dollar canadien
"EUR": 0.91 # Euro
}
# Cellule 4 : Fonction de conversion
def convert_currency(amount, from_currency, to_currency):
rate = exchange_rates[to_currency] / exchange_rates[from_currency]
return amount * rate
# Cellule 5 : Ajouter des colonnes converties (avec potentiel bug)
for currency, rate in exchange_rates.items():
tips[f"total_bill_{currency}"] = tips["total_bill"].apply(
lambda x: x * rate
)
Debug a cell
Rather than clicking on the “Run Cell” button (▶), click on the “Debug Cell” button (🐛) which appears to the left of the run button. VS Code starts a debugging session for this specific cell.
You can put breakpoints inside cells and use all the usual features of the VS Code debugger: Variables, Watch, Call Stack, etc.
Important: Debugging works between cells — variables defined in previous cells are accessible in the debugging session of subsequent cells.
5.7 Useful VS Code Extensions
VS Code has extensions that integrate into the editor to display recommendations from Pylint, Black and isort directly in the code, without going through the terminal.
Pylint Extension
Installation: search for “Pylint” in the Extensions panel and install the Microsoft version.
Once installed, the problems detected by Pylint appear directly in the editor:
- Yellow wavy lines: Warnings
- Blue wavy lines: convention suggestions
- Problems tab: complete list of problems with navigation possibility
To suppress a Pylint warning on a specific line:
investment = get_investment_info(inv) # pylint: disable=W0621
To disable a rule for an entire file, add at the top of the file:
# pylint: disable=missing-docstring
Extension Black Formatter
Installation: look for “Black Formatter” in the Extensions panel (Microsoft version).
Configuration in .vscode/settings.json to automatically format when saved:
{
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter",
"editor.formatOnSave": true
}
}
With this configuration, each time you save a Python file (Ctrl+S), Black automatically reformats the code according to its style rules.
Isort extension
Installation: search for “isort” in the Extensions panel (Microsoft version).
Configuration to sort imports on backup:
{
"[python]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
}
}
}
isort classifies imports into three groups according to PEP-8:
# Imports standard library
import os
import sys
from datetime import datetime
# Imports tiers (packages installés)
import fastapi
import sqlmodel
from seaborn import load_dataset
# Imports locaux (votre propre code)
from investment import Investment
from portfolio import Portfolio
from utilities import get_current_price
5.8 Module Summary
In this advanced module, you saw:
- Logpoints: log messages in the Debug Console without stopping execution and without modifying the source code — an excellent alternative to
print() - Launch configurations: save debug configurations in
.vscode/launch.jsonto always launch the right file and manage multiple scenarios (local, Docker, SSH) - Debugging web applications: use VS Code’s FastAPI/Django templates to easily start debugging REST APIs
- Docker debugging: add
debugpyin container, expose port 5678, and attach VS Code after startup - SSH debugging: SSH tunnel + “Remote Attach” configuration to debug on a remote server
- Jupyter debugging: debug notebook cells with breakpoints, benefiting from all the features of VS Code
- Useful extensions: Pylint (static analysis in the editor), Black (auto formatting), isort (organization of imports)
6. Use PyCharm to debug Python
Module duration: 14m 36s
6.1 PyCharm Editions
PyCharm is a Python IDE developed by JetBrains, the company behind ReSharper (for .NET). It is a high quality tool and a commercial product.
Community Edition (Free)
- Downloadable for free from the JetBrains website
- No limitations on the type of Python applications you can create
- Requires configuring the IDE manually (Python environments, linters, etc.)
- Ideal for most Python developers
Professional Edition (Paid)
- Annual cost per subscription (individual or enterprise license)
- Built-in support for Django, FastAPI, Flask and other web frameworks
- Jupyter Notebook support
- Remote Development: native connection to SSH, WSL servers, Docker containers
- Project templates for frameworks
- Integration with databases
For this course, the Community Edition is sufficient except for the remote debugging demo, which requires the Professional Edition.
Old Educational Edition
JetBrains formerly offered an “Educational Edition” of PyCharm, essentially the Community Edition with features for educational environments (course projects, etc.). These features have been integrated into current editions.
6.2 Basic debugging features in PyCharm
Notable difference with VS Code: entry point auto-detection
PyCharm automatically recognizes files containing an entry point (if __name__ == "__main__":). The first time it is run, PyCharm automatically creates a Run Configuration for this file. So no matter what file is open in the editor, PyCharm always launches app.py — unlike VS Code which requires manually creating a launch.json.
Debug interface
PyCharm’s interface is different from VS Code but covers the same functionalities:
- Debug button (insect icon): separate from the Run button
- Debug window: opens at the bottom, with tabs for variables, console, etc.
- Frames: equivalent of the Call Stack
- Variables: list of variables of the current frame, with navigation in complex structures
Set breakpoints
Like in VS Code, just click in the gutter. Breakpoints are represented by red circles.
# portfolio.py
class Portfolio:
def summarize(self):
total = 0.0
for investment in self.investments: # ← breakpoint sur cette ligne (ligne 30)
price = get_current_price(investment.coin)
total += investment.quantity * price
return total
Debug Toolbar
The debugging toolbar in PyCharm offers the same buttons as VS Code:
| Button | Shortcut | Action |
|---|---|---|
| Step Over | F8 | Execute the current line without entering functions |
| Step Into | F7 | Enters the function called on the current line |
| Step Out | Shift+F8 | Exit of current function |
| Summary | F9 | Continue to the next breakpoint |
| Stop | Ctrl+F2 | Stops debugging session |
Navigate Frames
The Frames section of PyCharm works like the Call Stack of VS Code. Click on a frame:
- Show the source code at the corresponding line
- Updates the Variables section with the variables of the selected frame
6.3 Inspecting complex data structures
A strong point of PyCharm compared to VS Code and pdb is its ability to inspect complex data structures in a very intuitive way.
Object navigation
In the Variables window, objects can be expanded recursively:
- Class instances show all their attributes
- Lists show each element with its index
- Dictionaries show every key/value pair
Watch Expressions directly from variables
In PyCharm, you can add a watch expression directly by right-clicking a variable in the Variables section and selecting “Add to Watches”. It’s faster than with VS Code where you have to manually type the expression.
Conditional breakpoints in PyCharm
Right click on a breakpoint (red circle in the gutter) > Edit Breakpoint:
# Exemple de condition pour le breakpoint :
investment.coin == "ethereum"
PyCharm displays a widget directly in the editor to enter the condition, make it dependent on another breakpoint, or configure log actions.
Logpoints in PyCharm
In the breakpoint configuration widget (right click > Edit Breakpoint), by checking “Evaluate and log” and entering an expression, the breakpoint becomes a logpoint: it displays the result of the expression in the debugging console without suspending execution.
6.4 Remote debugging in PyCharm via SSH
Remote debugging is a feature of PyCharm Professional Edition.
Remote connection options
PyCharm supports several remote connection modes:
| Method | Description |
|---|---|
| SSH | Connecting to a Linux Server via SSH |
| WSL | Windows Subsystem for Linux (Windows only) |
| JetBrains Space | JetBrains cloud service (source control, CI/CD) |
| Docker | Beta at training time |
Note: WSL is popular for Python development on Windows, combined with VS Code. Since WSL is only available on Windows, it is not covered in this course.
SSH connection in PyCharm
- When PyCharm opens, in the Remote Development section, click on SSH
- Click on the “New Project” drop-down menu > “Connect to Host”
Important: The remote host must be a Linux machine.
- Enter:
- Host: IP address or host name of the server (ex:
192.168.56.10) - Username: the SSH user (ex:
vagrant)
- Enter password when prompted
- PyCharm opens a new window connected to the remote server
The experience is as if you were working locally: editor, integrated terminal, debugger, everything works the same — but on the remote server.
# Exemple de configuration d'une machine Vagrant pour les démos :
# Vagrantfile
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/focal64"
config.vm.network "private_network", ip: "192.168.56.10"
config.vm.provision "shell", inline: <<-SHELL
apt-get update
apt-get install -y python3 python3-pip
pip3 install debugpy fastapi sqlmodel uvicorn
SHELL
end
6.5 PyCharm Plugins
Like VS Code with its extensions, PyCharm is extensible via plugins available in the JetBrains Marketplace.
Pylint plugin for PyCharm
Installation:
- Go to File > Settings > Plugins (or PyCharm > Preferences > Plugins on macOS)
- Marketplace tab
- Search for “Pylint”
- Install the plugin and restart PyCharm
Once installed, Pylint integrates into the IDE. A “Pylint” tab appears in the bottom bar. You can:
- Analyze the current file or the entire project
- View issues listed with their category and code
- Double-click on a problem to go directly to the affected line
Pylint recommendations also appear as marks in the editor (underlines), similar to VS Code.
Other useful plugins for PyCharm
| Plugin | Description |
|---|---|
| Black | Python Code Trainer |
| Mypy | Checking type annotations |
| isort | Automatic organization of imports |
| .env files support | Coloring and support for .env files |
| Docker | Docker integration in IDE |
6.6 Module and course summary
PyCharm module summary
In this module, you discovered:
- PyCharm, a commercial Python IDE from JetBrains, available in Community Edition (free) and Professional Edition (paid)
- Entry point auto-detection: PyCharm automatically creates a launch configuration for files with
if __name__ == "__main__":, avoiding the problem encountered in VS Code - Basic debugging features: breakpoints, stepping, variables, frames — similar to VS Code with a different interface
- Navigating complex data structures: PyCharm facilitates recursive inspection of nested objects, lists and dictionaries
- watch expressions: can be added directly from the Variables panel
- Remote debugging via SSH (Professional Edition): transparent connection to a Linux server from PyCharm
- Plugins: extension of PyCharm with tools like Pylint for static analysis
General course summary
This course introduced you to three complementary tools for debugging Python applications:
1. pdb — The standard library debugger
- Available wherever Python is installed
- Command line interface with text commands
- Need to add
import pdb; pdb.set_trace()in code - Ideal for GUI-less environments or for fast server-based debugging
2. Visual Studio Code — A modern, extensible editor
- GUI that replaces most
pdbcommands - The Run and Debug panel centralizes variables, call stack, breakpoints and watch expressions
- Advanced support: logpoints, launch configurations, Docker/SSH/Jupyter debugging
- Recommended for daily development, including work on WSL
3. PyCharm — A complete Python IDE
- Very comprehensive graphical debugging experience – Particularly strong for inspecting complex data structures
- Integrated and Transparent Remote Development (Professional Edition)
- Extension via plugins
Additional tools presented
| Tool | Type | Role |
|---|---|---|
| Pylint | Static analyzer | Identify errors and improve code quality |
| ipdb | Improved debugger | pdb with IPython (syntactic coloring, completion) |
| Black | Trainer | Automatically apply PEP-8 style rules |
| isort | Import Organizer | Sort and organize Python imports |
| mypy | Type checker | Check type annotations before execution |
| debugpy | Debug Adapter | Allow VS Code to connect to remote Python |
Next steps
To deepen your knowledge after this course, Pluralsight offers:
- Working with Databases in Python 3 — to master data access with Python
- Web Development with Bottle 0.12 Fundamentals — for web development with Python
Search Terms
debugging · python · foundations · data · analysis · engineering · analytics · pycharm · debug · command · aka · breakpoints · pdb · pylint · extension · launch.json · application · commands · expressions · remote · stack · breakpoint · conditional · container