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
文件:
点击创建launch.json文件链接。
从调试选项列表中选择Python Debugger。
- 从弹出的配置类型选择列表中,根据需要选择:如果调试单个Python脚本,选择Python文件。其他类型及选项在列表中有说明,如下:
- 这时候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 | $ pip install --upgrade debugpy |
虽然不是必须的,但使用虚拟环境仍然是推荐的最佳实践。在VS Code中,可以打开命令面板(
Ctrl+Shift+P
),运行**Python: 创建环境…**命令创建虚拟环境。
命令行语法
调试器命令行语法如下:
1 | python -m debugpy |
示例
在命令行,可以使用一个特定的端口(5678)启动要调试脚本的调试器。如下:
1 | python -m debugpy --listen 5678 ./myscript.py |
这个示例假设这个脚本是长期运行且忽略--wait-for-client
标识,意味着脚本不会等待客户端连结。
然后,VS Code调试器插件使用下面的配置连结。
1 | { |
提示:可以指定监听的主机,默认使用127.0.0.1。
如果调试远程机器上的远程代码或运行在docker容器中的代码,需要修改前文的 CLI 命令指定一台机器。
1 | python -m debugpy --listen 0.0.0.0:5678 ./myscript.py |
关联的配置文件看起来如下:
1 | { |
提示:注意,如果指定了
127.0.0.1
或localhost
以外的机器,就打开了一个从任何机器允许访问的一个端口,这将带来安全风险。在远程调试时,需确保已采取了适合的预防措施,例如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调试器就连结它:
- 运行VS Code,打开这个脚本所在的文件夹或工作空间,如果
launch.json
不存在则创建它。 - 在脚本代码中,添加下面的代码并保存:
1 | import debugpy |
- 使用终端:创建新的终端命令创建一个终端,终端会自动激活脚本选择的环境。
- 在终端中安装debugpy(见安装debugpy一节)。
- 在终端中,用该脚本开启Python,如
python3 myscript.py
。会看到包含在代码中的“Waiting for debugger attach”信息,脚本会在debugpy.wait_for_client()
处等待调用。 - 切换至运行和调试(
Ctrl+Shift+D
)面板,从调试器下拉列表中选择合适的配置进入调试器。 - 调试器在
debugpy.breakpoint()
处等待调用,在该处可以正常的使用调试器。也可以使用UI界面设置其他断点,而不是用debugpy.breakpoint()
。
用SSH调试远程脚本
远程调试能力允许在本地VS Code内单步调试远程服务器上运行的程序,而不需要在远程服务器安装VS Code。为了安全,在连接远程服务器调试时需要使用安全连接,如SSH。
提示:在Windows系统上,需要安装Windows 10 OpenSSH才可以使用
ssh
命令。
下面的步骤简要列出了设置SSH隧道的步骤。与开放一个可公共访问的端口比,SSH隧道允许安全的在本地连接远程机器进行工作。
在远程主机上:
- 要启用端口,打开
sshd_config
配置文件(Linux服务器在/etc/ssh/
下,Windows在%programfiles(x86)%/openssh/etc
下),添加或修改以下内容:
1 | AllowTcpForwarding yes |
提示:AllowTcpForwarding默认值为yes,可能不需要修改。
- 如果必须添加
AllowTcpForwarding
,则需要重启SSH服务。在Linux/macOS上,运行sudo service ssh restart
;在Windows上,运行services.msc
,在服务列表中选择OpenSSH或sshd。
在本地主机上:
- 执行
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
标识指定认证文件的路径。 - 确定在SSH会话中可以看到提示信息。
- 在VS Code工作空间中,在
launch.json
文件中为远程调试创建配置项,设置匹配ssh
命令使用的端口号,并设置主机为localhost
。使用localhost
是因为已经设置了SSH隧道。
1 | { |
开始调试
现在已经设置了连接远程主机的SSH隧道,可以开始调试了。
- 远程和本地主机:保证可以获取相同的代码。
- 远程和本地主机:安装debugpy(见安装debugpy一节)。
- 远程主机:有两种指定连结远程主机的方式。
a. 在源代码中,添加下面的代码,address
替换为远程主机的IP和端口(这里用1.2.3.4作为示例)。
1 | import debugpy |
在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 | #import debugpy |
- 本地机器:切换至运行和调试(Ctrl+Shift+D)面板,选择Python Debugger: 远程附加配置。
- 本地机器:在打算调试的代码位置设置一个断点。
- 本地机器:使用修改过的Python Debugger:附加配置文件和启动调试按钮启动VS Code调试器。VS Code会停在本地设置的断点位置,允许单步调试代码、检查变量并执行其他调试动作。在Debug Console中输入的表达式也会在远程主机上运行。
像来自
- 在远程调试时,调试工具栏如下面:
在工具栏上,断开连接按钮(Shift+F5
)可以停止调试器,并允许远程程序运行完成。重启按钮(Ctrl+Shift+F5
)重启调试器本地主机上的调试器,但不会重启远程程序。只有在已经重启远程程序并需要重新连结调试器时使用重启按钮。
设置配置项
当第一次创建launch.json
时,有两种标准配置可以在集成终端(VS Code内)或外部终端(VS Code外部)运行编辑器中活动的文件:
1 | { |
具体设置将在下面的章节中描述。也可以添加其他不在标准配置中的设置,如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 | { |
提示:当调试器重新加载时,导入代码可能会再次执行。要避免这种情况,尝试只在模块中使用导、常量和定义,将所有代码放到函数中。可选的,用
if __name__=="__main__"
做检查。
subprocess
指定是否启用子进程调试。默认是false,设置为true启用。获取更多信息见multi-target debugging。
cwd
为调试器指定当前工作目录,作为其他任何相对路径的基础目录。如果忽略,默认为${workspaceFolder}
(VS Code打开的文件夹)。
例如,${workspaceFolder}
包含的一个有app.py
的py_code
文件夹,和有salaries.csv
的data
文件夹。如果在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 | { |
如上配置,指定"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 | { |
常见问题
为什么调试器不工作有很多原因。有时调试控制台会显示出原因,但主要的原因如下:
- 打开扩展面板(
Ctrl+Shift+X
)并搜索@installed python debugger
,确保Python调试器扩展已安装并已启用。 - 指向可执行程序python的路径是否正确:通过运行Python:选择解释器命令并查看当前值,检查选择的解释器路径。
- 在
launch.json
文件中"type"
设置为过时的值"python"
:用Python调试器正常工作的"debugpy"
替代"python"
。 - 在监视窗口中有无效的表达式:清理监视窗口中的所有表达式并重启调试器。
- 如果在使用本地现成API的多线程应用上工作(例如Win32的
CreateThread
函数,而不是Python的线程API),非常有必要在每个要调试文件的头部加入以下代码:
1 | import debugpy |
- 如果在Linux系统中工作,当尝试应用调试器至任何运行中的进程时,可能会收到“timed out”错误信息。要防止该错误,可以临时运行下面的命令:
1 | echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope |