VS Code中Python代码调试

Python扩展通过Python调试器扩展支持多种类型的Python应用调试。简短了解基本调试,见Tutorial - Configure and run the debugger。此外也查看Flask tutorial。两个手册展示了像设置断点和步入代码核心技能。

学习通用的调试特性,像检查变量、设置断点和其他不依赖语言的活动,回顾VS Code debugging

本文主要讲述Python特定的调试配置,包含特定应用类型和远程调试必要的步骤。

Python调试器扩展

在VS Code中,Python调试器扩展会随着Python扩展一起安装。Python调试器基于实现了Python调试适配器协议的debugpy模块,可以调试多种类型的Python应用,包括脚本、Web应用、远程进程等。

验证上面插件安装的方法是,打开扩展面板Ctrl+Shift+X),在搜索框中输入@installed python debugger,在结果列表中可以看到Python和Python调试器插件。

初始配置

VS Code通过配置控制调试行为。这些配置在launch.json文件中进行定义。launch.json文件存储在工作空间的.vscode目录中。

初始化调试配置,首先打开运行和调试面板Ctrl+Shift+D)。如果还未做配置,会看到运行和调试按钮和创建launch.json文件的超链接。

按照以下步骤创建launch.json文件:

  1. 点击创建launch.json文件链接。

  2. 从调试选项列表中选择Python Debugger

  1. 从弹出的配置类型选择列表中,根据需要选择:如果调试单个Python脚本,选择Python文件。其他类型及选项在列表中有说明,如下:

  1. 这时候Python调试器扩展会创建并打开launch.json文件。根据上一步骤选择的类型,文件中已经预先定义了一些配置,如本例中选择的Python文件。可以根据需要修改文件内容添加配置(如添加参数)。

下文会讲解配置细节和不同类型应用的配置。

附加配置

默认的,VS Code只展示Python调试器扩展最通用的配置。可点击其他配置下拉框中选择添加配置命令或点击launch.json文件编辑器中的添加配置按钮,VS Code会展示所有可选配置的列表(注意选择Python调试程序选项):

选择带有参数的Python文件后,效果如下:

调试的时候,状态栏会显示当前的配置和当前的解释器。点击状态栏上的配置,可以选择其他配置。

在工作空间下,默认调试器跟其他扩展一样使用相同的解释器。如果调试器要使用不同的解释器,设置launch.json中的python值,或者使用状态栏的Python解释器指示器。

基础调试

如果只是调试Python脚本,最简单的方式是脚本编辑器运行下拉列表中选择Python调试程序:调试Python文件

如果调试使用Flask、Django或FastAPI开发的web应用,Python调试器扩展在运行和调试面板中,提供了根据项目结构提供动态调试配置的选项。

如果调试其他类型应用,可以通过点击运行和调试面板中的运行和调试按钮。

当没有设置配置时,VS Code会给出调试选项,选择适合的选项快速进入调试。有两个常用的选项,Python文件配置运行当前打开的Python文件,或者使用进程ID进行附加配置连结调试器到一个运行中的进程。添加配置后,就可以在列表中选择,并使用开始调试(F5)进行调试。

命令行调试

如果安装了debugpy也可以在命令行运行调试器。

安装debugpy

使用以下命令安装debugpy

1
2
3
4
5
6
7
8
9
10
$ pip install --upgrade debugpy
Looking in indexes: https://mirrors.aliyun.com/pypi/simple/
Collecting debugpy
Downloading https://mirrors.aliyun.com/pypi/packages/fd/b6/ee71d5e73712daf8307a9e85f5e39301abc8b66d13acd04dfff1702e672e/debugpy-1.8.5-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.4 MB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.4/1.4 MB 11.6 MB/s eta 0:00:00
Installing collected packages: debugpy
Successfully installed debugpy-1.8.5

[notice] A new release of pip is available: 24.0 -> 24.2
[notice] To update, run: pip install --upgrade pip

虽然不是必须的,但使用虚拟环境仍然是推荐的最佳实践。在VS Code中,可以打开命令面板(Ctrl+Shift+P),运行**Python: 创建环境…**命令创建虚拟环境。

命令行语法

调试器命令行语法如下:

1
2
3
4
5
6
7
8
python -m debugpy
--listen | --connect
[<host>:]<port>
[--wait-for-client]
[--configure-<name> <value>]...
[--log-to <path>] [--log-to-stderr]
<filename> | -m <module> | -c <code> | --pid <pid>
[<arg>]...

示例

在命令行,可以使用一个特定的端口(5678)启动要调试脚本的调试器。如下:

1
python -m debugpy --listen 5678 ./myscript.py

这个示例假设这个脚本是长期运行且忽略--wait-for-client标识,意味着脚本不会等待客户端连结。

然后,VS Code调试器插件使用下面的配置连结。

1
2
3
4
5
6
7
8
9
{
"name": "Python Debugger: Attach",
"type": "debugpy",
"request": "attach",
"connect": {
"host": "localhost",
"port": 5678
}
}

提示:可以指定监听的主机,默认使用127.0.0.1。

如果调试远程机器上的远程代码或运行在docker容器中的代码,需要修改前文的 CLI 命令指定一台机器。

1
python -m debugpy --listen 0.0.0.0:5678 ./myscript.py

关联的配置文件看起来如下:

1
2
3
4
5
6
7
8
9
{
"name": "Attach",
"type": "debugpy",
"request": "attach",
"connect": {
"host": "remote-machine-name", // replace this with remote machine name
"port": 5678
}
}

提示:注意,如果指定了127.0.0.1localhost以外的机器,就打开了一个从任何机器允许访问的一个端口,这将带来安全风险。在远程调试时,需确保已采取了适合的预防措施,例如SSH隧道。

命令行选项

标识 选项 描述
–listen or –connect [<host>:]<port> 必须。为调试适配器服务器指定主机地址和端口号,来等待连接(–listen)或连接一个等待连接的客户端(–connect) 。这跟VS Code调试配置使用的地址相同。默认,主机地址是localhost (127.0.0.1)
–wait-for-client none 可选。 指定从调试服务器有一个连接连入时才运行代码。这个设置允许从第一行代码开始调试。
–log-to <path> 可选。指定保存日志的一个已存在目录。
–log-to-stderr none 可选。启用 debugpy 写日志到标准错误输出。
–pid <pid> 可选。指定一个已经运行中待调试服务器进入的进程。
–configure- <value> 可选。设置在客户端连接前调试服务器必须知道的调试属性。这些属性可以直接在调用配置中直接使用,但是必须以这种方式设置的连结配置。例如,不想调试服务器自动引入连结的进程创建的子进程,使用--configure-subProcess false

提示:可以使用[<arg>]将命令行参数传递给已启动的应用程序。

通过网络连结调试

本地脚本调试

在某些实例中,需要调试由另外一个进程在本地调用的Python脚本。例如,调试一个web服务器运行的一些处理特定任务的脚本。在这个例子中,一旦这个脚本被调用则需要VS Code调试器就连结它:

  1. 运行VS Code,打开这个脚本所在的文件夹或工作空间,如果launch.json不存在则创建它。
  2. 在脚本代码中,添加下面的代码并保存:
1
2
3
4
5
6
7
8
import debugpy

# 5678 is the default attach port in the VS Code debug configurations. Unless a host and port are specified, host defaults to 127.0.0.1
debugpy.listen(5678)
print("Waiting for debugger attach")
debugpy.wait_for_client()
debugpy.breakpoint()
print('break on this line')
  1. 使用终端:创建新的终端命令创建一个终端,终端会自动激活脚本选择的环境。
  2. 在终端中安装debugpy(见安装debugpy一节)。
  3. 在终端中,用该脚本开启Python,如python3 myscript.py。会看到包含在代码中的“Waiting for debugger attach”信息,脚本会在debugpy.wait_for_client()处等待调用。
  4. 切换至运行和调试(Ctrl+Shift+D面板,从调试器下拉列表中选择合适的配置进入调试器。
  5. 调试器在debugpy.breakpoint()处等待调用,在该处可以正常的使用调试器。也可以使用UI界面设置其他断点,而不是用debugpy.breakpoint()

用SSH调试远程脚本

远程调试能力允许在本地VS Code内单步调试远程服务器上运行的程序,而不需要在远程服务器安装VS Code。为了安全,在连接远程服务器调试时需要使用安全连接,如SSH。

提示:在Windows系统上,需要安装Windows 10 OpenSSH才可以使用ssh命令。

下面的步骤简要列出了设置SSH隧道的步骤。与开放一个可公共访问的端口比,SSH隧道允许安全的在本地连接远程机器进行工作。

在远程主机上:

  1. 要启用端口,打开sshd_config配置文件(Linux服务器在/etc/ssh/下,Windows在%programfiles(x86)%/openssh/etc下),添加或修改以下内容:
1
AllowTcpForwarding yes

提示:AllowTcpForwarding默认值为yes,可能不需要修改。

  1. 如果必须添加AllowTcpForwarding,则需要重启SSH服务。在Linux/macOS上,运行sudo service ssh restart;在Windows上,运行services.msc,在服务列表中选择OpenSSH或sshd。

在本地主机上:

  1. 执行ssh -2 -L sourceport:localhost:destinationport -i identityfile user@remoteaddress创建一个SSH隧道,为destinationport选择一个端口,并在user@remoteaddress中指定合适的用户名和远程主机的IP。例如,在IP地址1.2.3.4上使用端口5678,命令为ssh -2 -L 5678:localhost:5678 -i identityfile user@1.2.3.4。可以使用-i标识指定认证文件的路径。
  2. 确定在SSH会话中可以看到提示信息。
  3. 在VS Code工作空间中,在launch.json文件中为远程调试创建配置项,设置匹配ssh命令使用的端口号,并设置主机为localhost。使用localhost是因为已经设置了SSH隧道。
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"name": "Python Debugger: Attach",
"type": "debugpy",
"request": "attach",
"port": 5678,
"host": "localhost",
"pathMappings": [
{
"localRoot": "${workspaceFolder}", // Maps C:\Users\user1\project1
"remoteRoot": "." // To current working directory ~/project1
}
]
}

开始调试

现在已经设置了连接远程主机的SSH隧道,可以开始调试了。

  1. 远程和本地主机:保证可以获取相同的代码。
  2. 远程和本地主机:安装debugpy(见安装debugpy一节)。
  3. 远程主机:有两种指定连结远程主机的方式。

a. 在源代码中,添加下面的代码,address替换为远程主机的IP和端口(这里用1.2.3.4作为示例)。

1
2
3
4
5
6
7
import debugpy

# Allow other computers to attach to debugpy at this IP address and port.
debugpy.listen(('1.2.3.4', 5678))

# Pause the program until a remote debugger is attached
debugpy.wait_for_client()

listen中应该使用远程主机的私有IP。然后可以正常启动程序,直到调试器连结使其暂停。

b. 通过debugpy启动远程进程,例如:

1
python3 -m debugpy --listen 1.2.3.4:5678 --wait-for-client -m myproject

以上命令使用python3启动myproject,使用远程主机的私有IP1.2.3.4,并监听端口5678(也可以不使用-m,而是指定文件路径启动远程Python进程,例如./hello.py)。

  1. 本地主机:只有在上面列出的远程主机上的代码被修改,然后在远程主机的源代码中,拷贝添加注释的相同代码。添加这些代码确保远程和本地主机上的代码每行都是匹配的。
1
2
3
4
5
6
7
#import debugpy

# Allow other computers to attach to debugpy at this IP address and port.
#debugpy.listen(('1.2.3.4', 5678))

# Pause the program until a remote debugger is attached
#debugpy.wait_for_client()
  1. 本地机器:切换至运行和调试(Ctrl+Shift+D)面板,选择Python Debugger: 远程附加配置。
  2. 本地机器:在打算调试的代码位置设置一个断点。
  3. 本地机器:使用修改过的Python Debugger:附加配置文件和启动调试按钮启动VS Code调试器。VS Code会停在本地设置的断点位置,允许单步调试代码、检查变量并执行其他调试动作。在Debug Console中输入的表达式也会在远程主机上运行。

像来自print表达式的内容,输出到标准输出的文本,会出现在两边的主机上。但是其他输出,像来自matplotlib包的图像,只会出现在远程主机上。

  1. 在远程调试时,调试工具栏如下面:

在工具栏上,断开连接按钮(Shift+F5)可以停止调试器,并允许远程程序运行完成。重启按钮(Ctrl+Shift+F5)重启调试器本地主机上的调试器,但不会重启远程程序。只有在已经重启远程程序并需要重新连结调试器时使用重启按钮。

设置配置项

当第一次创建launch.json时,有两种标准配置可以在集成终端(VS Code内)或外部终端(VS Code外部)运行编辑器中活动的文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"configurations": [
{
"name": "Python Debugger: Current File (Integrated Terminal)",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal"
},
{
"name": "Python Debugger: Current File (External Terminal)",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "externalTerminal"
}
]
}

具体设置将在下面的章节中描述。也可以添加其他不在标准配置中的设置,如args

提示:在项目中,创建一个运行特定启动文件的配置会很有帮助。例如,启动调试器时总是使用参数--port 1593调用startup.py,如下创建一个配置:

1
2
3
4
5
6
7
{
"name": "Python Debugger: startup.py",
"type": "debugpy",
"request": "launch",
"program": "${workspaceFolder}/startup.py",
"args" : ["--port", "1593"]
},

name

给展示在VS Code调试配置下拉列表中的配置命名。

type

标识调试器的类型;保持该设置为debugpy来调试Python代码。

request

指定开始调试的模式:

  • launch:在指定为program的文件上启动调试器。
  • attach:附加调试器至已运行的一个进程。参见“通过网络连结调试”一节。

program

提供Python程序入口模块(启动文件)的全路径。在默认配置中经常使用${file},用作编辑器当前活动的文件。指定一个特定的启动文件时,需要确保不管打开的是哪个文件,都用同一个入口调用程序。例如:

1
"program": "/Users/Me/Projects/MyProject/src/event_handlers/__init__.py",

也可基于工作空间根目录设置相对路径。例如,工作空间根目录是/Users/Me/Projects/MyProject,则可以使用下面的配置:

1
"program": "${workspaceFolder}/src/event_handlers/__init__.py",

module

提供给被调试模块指定名称的能力,跟命令行运行时的-m参数类似。更多信息见Python.org

python

指向调试使用的Python解释器的全路径。

如果不指定,这个设置默认指向工作空间选择的解释器,跟${command:python.interpreterPath}等同。使用其他解释器,则指定python值为解释器的路径。

可选的,可以在不同平台上使用包含使用的Python解释器路径的环境变量,因此就不再需要目录路径了。

如果需要传递参数给Python解释器,可以使用pythonArgs属性。

pythonArgs

用语法"pythonArgs": ["<arg 1>", "<arg 2>",...]指定传递给Python解释器的参数。

args

指定传递给Python程序的参数。每个用空格分开的参数字符串需在引号内,如:

1
"args": ["--quiet", "--norepeat", "--port", "1593"],

如果想每次调试运行提供不同的参数,可以设置args${command:pickArgs}。每次启动调试时,将提示输入变量。

stopOnEntry

设置为true时,在程序第一行代码被调试时中断调试器。如果忽略(默认)或设置为false,调试器运行程序至第一个断点。

console

redirectOutput默认值没被修改时,指定程序输出如何展示。

显示输出信息的地方
"internalConsole" VS Code调试控制台。如果 redirectOutput 设置为False,不展示输出信息。
"integratedTerminal" (默认) VS Code集成终端。如果redirectOutput 设置为True,在调试控制台也会展示输出信息。
"externalTerminal" 独立的控制台窗口。如果redirectOutput 设置为True,在调试控制台也会展示输出信息。

purpose

使用purpose选项,不止一种方法配置运行按钮。设置选项值为debug-test,定义当在VS Code中调试单元测试时使用该配置。然而,设置该选项为debug-in-terminal,定义只有在访问编辑器右上方的运行Python文件按钮时才使用该配置(无论是该按钮的运行Python文件还是调试Python文件选项被使用)。注意purpose选项不能用来通过F5运行→启动调试启动调试器

autoReload

允许在调试器碰到断点后,代码做了修改时自动重新加载调试器。如下面代码设置{"enable": true}启用这个特性:

1
2
3
4
5
6
7
8
9
10
{
"name": "Python Debugger: Current File",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"autoReload": {
"enable": true
}
}

提示:当调试器重新加载时,导入代码可能会再次执行。要避免这种情况,尝试只在模块中使用导、常量和定义,将所有代码放到函数中。可选的,用if __name__=="__main__"做检查。

subprocess

指定是否启用子进程调试。默认是false,设置为true启用。获取更多信息见multi-target debugging

cwd

为调试器指定当前工作目录,作为其他任何相对路径的基础目录。如果忽略,默认为${workspaceFolder}(VS Code打开的文件夹)。

例如,${workspaceFolder}包含的一个有app.pypy_code文件夹,和有salaries.csvdata文件夹。如果在py_code/app.py上启动调试器,则指向data文件的相对路径就非常依赖cwd的值:

cwd data的相对路径
忽略 or ${workspaceFolder} data/salaries.csv
${workspaceFolder}/py_code ../data/salaries.csv
${workspaceFolder}/data salaries.csv

redirectOutput

设置为true时(默认internalConsole),调试器打印程序所有输出至VS Code调试输出窗口。如果设置为false(默认integratedTerminal和externalTerminal),程序输出不会展示在调试输出窗口。

使用"console": "integratedTerminal""console": "externalTerminal"时,该选项通常被禁用,因为不需要重复输出到调试控制台。

justMyCode

当忽略或设置为true时(默认),限制只调试用户代码。设置为false时也允许调试标准库函数。

django

设置为true时,激活调试Django web框架的特性。

sudo

设置为true且使用"console": "externalTerminal"时,允许调试需要提升权限的应用。使用一个外部控制台捕获密码是必要的。

pyramid

当设置为true时,确保使用必要的pserve命令调用Pyramid应用。

env

在系统环境变量之上,为调试器进程设置总是继承的可选环境变量。这些参数的值必须作为字符串输入。

envFile

指向包含环境变量定义的一个可选文件路径。见:Configuring Python environments - environment variable definitions file

gevent

如果设置为true,启用调试gevent monkey-patched code

jinja

当设置为true,激活Jinja模板框架调试特性。

断点和日志点

Python调试器扩展支持为调试代码设置断点日志点。简短了解基础的调试和断点使用,Tutorial - Configure and run the debugger

条件断点

断点也可以被设置在表达式、命中计数或两者组合满足时被触发。Python调试器扩展支持命中计数是整数,进而支持==、>、>=、<、<=和%操作。例如,设置命中次数>5,会在发生5次后设置的断点被触发。获取更多信息,见conditional breakpoints

在代码中调用断点

调试会话中,可以在想要暂停调试器的位置调用debugpy.breakpoint()

断点验证

Python调试器扩展会自动探测在非可执行行设置的断点,类似pass表达式或多行表达式的中间。在这些场景中,调试器会移动断点至最近的代码行,确保代码执行停在这个断点。

调试特定的应用类型

配置下拉表中为一般应用提供了多种不同的选项:

配置 描述
Attach 见前文“通过网络连结调试”一节。
Django 指定"program": "${workspaceFolder}/manage.py""args": ["runserver"],并且添加"django": true启用Django HTML模板的调试。
Flask 见下文“Flask调试”一节。
Gevent 添加 "gevent": true 到集成的标准终端配置。
Pyramid 移除program,添加"args": ["${workspaceFolder}/development.ini"],添加"jinja": true,启用模板调试,并且添加"pyramid": true,确保使用必要的pserve命令调用Pyramid应用。

远程调试和Google App Engine需要特定的步骤。了解调试单元测试的详情,见Testing

调试需要管理员权限的应用,使用"console": "externalTerminal""sudo": "True"

Flask调试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"name": "Python Debugger: Flask",
"type": "debugpy",
"request": "launch",
"module": "flask",
"env": {
"FLASK_APP": "app.py"
},
"args": [
"run",
"--no-debugger"
],
"jinja": true
},

如上配置,指定"env": {"FLASK_APP": "app.py"}"args": ["run", "--no-debugger"]"module": "flask"属性替代program。(可能在env属性中看到"FLASK_APP": "${workspaceFolder}/app.py",在这个场景中修改配置只有文件名。然而,可能会看到“Cannot import module C”错误,C是一个驱动器字符。)

"jinja": true也是启用Flask默认的Jinja模板引擎调试的设置。

如果以开发模式运行Flask开发服务器,使用下面的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"name": "Python Debugger: Flask (development mode)",
"type": "debugpy",
"request": "launch",
"module": "flask",
"env": {
"FLASK_APP": "app.py",
"FLASK_ENV": "development"
},
"args": [
"run"
],
"jinja": true
},

常见问题

为什么调试器不工作有很多原因。有时调试控制台会显示出原因,但主要的原因如下:

  • 打开扩展面板(Ctrl+Shift+X)并搜索@installed python debugger,确保Python调试器扩展已安装并已启用。
  • 指向可执行程序python的路径是否正确:通过运行Python:选择解释器命令并查看当前值,检查选择的解释器路径。

  • launch.json文件中"type"设置为过时的值"python":用Python调试器正常工作的"debugpy"替代"python"
  • 在监视窗口中有无效的表达式:清理监视窗口中的所有表达式并重启调试器。
  • 如果在使用本地现成API的多线程应用上工作(例如Win32的CreateThread函数,而不是Python的线程API),非常有必要在每个要调试文件的头部加入以下代码:
1
2
import debugpy
debugpy.debug_this_thread()
  • 如果在Linux系统中工作,当尝试应用调试器至任何运行中的进程时,可能会收到“timed out”错误信息。要防止该错误,可以临时运行下面的命令:
1
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope