Python程序员Visual Studio Code指南5调试

5 调试

当运行程序时终端输出错误时,可以参考编辑器中的"问题"面板来解决遇到的问题。不过,并非所有错误都会导致错误。可能出现的情况是,程序执行成功,但输出结果与预期不同。出现这种情况时,下一步就是找出程序中的错误。这个过程被称为调试。

您可以尝试通过注释代码行(从而禁止代码块运行)、添加更多打印语句以在代码块执行后输出,或修改程序中的行来定位和解决问题。虽然每种方法都能帮助你找出错误和可能的修复方法,但这一过程效率很低。

Visual Studio Code有内置调试器,它的功能通过Python扩展得到了进一步扩展。虽然调试器可以帮助您识别和修复错误,但您仍有责任识别错误可能出现在代码的哪个位置。一旦你确定了错误的潜在位置,就可以使用调试器来帮助你跟踪程序的执行状态。

5.1 启动调试

要让调试器在执行过程中暂停,必须在代码的某一行设置断点。只要你想检查程序的运行状态,然后逐行检查代码,就可以设置断点。在 Visual Studio 代码中,断点会以红点的形式出现在编辑器空白处。当调试会话开始时,调试器会执行到断点为止的所有代码行,并高亮显示要执行的下一行。(如果是步进操作,则例外,这将在后面的 "调试命令 "一节中解释)。

要添加断点,请将鼠标悬停在当前代码行的编辑器边框上,然后单击以添加断点。或者,使用键盘快捷键F9来添加当前代码行。要移除断点,请在编辑器空白处选择断点,或再次按F9键。也可以在顶部菜单中选择运行,然后选择移除所有断点,从而移除所有断点。

你可以在会话期间暂停调试器的任何位置调用debugypy.breakpoint()来强制设置断点。如果要强制设置断点,必须在代码中导入debuypy。调用后,调试器会停止下一行代码。这种方法会在程序中硬编码断点。如果你有一些异步的回调函数,而你又不想用其他断点来设置、清除、启用或禁用它们,那么这种方法可能会很有用。通过对几个断点进行硬编码,就能在回调函数发生时捕捉到它们。

您可以通过以下方式启动调试会话:

  • 菜单: 选择运行 ➪ 启动调试。
  • 键盘快捷键: 按 F5。
  • 运行视图: 单击运行和调试

  • 运行视图: 单击 "开始调试"(在调试会话启动后出现)

编辑器在调试会话期间的行为由调试配置控制。调试配置是调试器功能的设置列表。Python 扩展提供了几种配置,稍后将在 "启动配置 "中探讨。本章练习中出现提示时,点击 Python 文件配置,调试当前活动的 Python 文件。

启动调试会话后,会打开运行视图。运行视图用于管理调试会话。当调试会话处于活动状态时,"运行 视图中的面板会根据正在执行的内容动态变化。

当代码中添加断点时,断点面板会将模块名称(如times_two.py)及其相应的断点行添加到列表中。目前times_two.py 中有两个断点,一个在第4行,另一个在第6行。在断点面板中选择其中一个断点,编辑器就会高亮显示该断点。

假设你在程序中设置了多个断点。在调试时,你决定在会话中只选择一些断点,这样就不必在每个断点都暂停调试器了。移除不需要的断点会删除断点,如果你打算在后续调试会话中保留断点,这可能不是你的本意。相反,你可以禁用断点。在 "断点"面板中,取消选中断点旁边的方框即可禁用断点。或者,你也可以右键单击断点,然后单击禁用断点。如果想一次性禁用所有断点,可以单击"停用断点"按钮。不过,如果您想删除所有断点,请单击"删除断点"按钮。删除所有断点有助于确保清除程序中设置的所有断点。

调试器运行时,变量的当前状态会反映在变量面板中。变量面板将变量分为局部和全局范围。

在继续调试的过程中,请注意面板中变量的变化情况。虽然变量会随着程序的执行而填充,但变量值有可能会产生错误,从而导致代码停止执行。如果你发现换一个值就能继续执行程序,你可以在变量面板中更改该值。要更改数值,请选中变量并按Enter键。输入新变量后,再按一次Enter键,将修改后的值保存到程序状态中。

5.2 调试命令

除运行视图外,编辑器中还会出现调试工具栏(见图 5.9)。调试工具栏提供了对这些调试命令的快速访问:

  • A—Continue (F5)
  • B—Step Over (F10)
  • C—Step Into (F11)
  • D—Step Out (Shift+F11)
  • E—Restart (Shift+Cmd/Ctrl+F5)
  • F—Stop (Shift+F5)

调试命令是一起工作的,而不是独立的;也就是说,你通常要使用多种命令的组合来调试不同的代码行。

除调试工具栏命令外,编辑器右键上下文菜单中还提供了其他命令。这些功能包括

  • 添加内联断点(Add Inline Breakpoint)-在代码中添加断点,特别是在光标下的代码中。这对单个语句中的复合表达式非常有用,因为您希望在表达式的特定部分断开。或者,您可以导航到运行 ➪ 新断点 ➪ 内联断点,或者使用键盘快捷键 Shift+F9。
  • 运行到光标(Run to Cursor)-运行一段代码而不设置另一个断点。
  • 跳转到光标(Jump to Cursor)-跳转代码行或返回并重复代码行。

5.2.1 Continue

当调试器停在某个断点时,单击"继续(Continue)"会运行该断点后的所有代码,直到下一个断点或程序结束。

5.2.2 Step Over

Step Over命令运行调试器当前暂停的代码行,然后自动暂停到下一行,无需另设断点。如果当前行是函数调用,调试器将运行整个函数,然后在函数调用后的下一行暂停。从本质上讲,Step Over命令是在当前范围内逐行进行调试。

试试看 运行调试器,逐行查看代码。注意,当调试器运行到 times_two() 函数定义处时,调试器的下一步是 print() 语句。

5.2.3 Step Into

当调试器在函数处暂停时,Step Into 命令会进入函数作用域。在这里,你可以查看函数作用域内的每一行,还能进入其他函数调用。

5.2.4 Step Out

如果发现自己想从函数内部退出到调用该函数的作用域,可以使用Step Out命令。

5.2.5 停止

在调试会话期间,可以使用Stop命令停止所有执行。停止会话会停止调试器,但不会结束程序。如果在调试会话期间发现了程序中的错误,并得出结论:如果继续执行,可能会产生影响程序的副作用,例如覆盖错误的文件。在这种情况下,请选择停止命令退出调试器。

5.2.5 重新启动

在调试和纠正程序中的错误时,你往往不想继续在当前(通常是错误的)状态下运行程序。这时,你需要停止执行并重新启动调试程序。重启(Restart)命令可以方便地停止调试器,保存当前文件,然后用最近的修改重新启动调试器。

参考资料

5.3 调用堆栈

模块及其函数调用被称为框架。帧相互堆叠,当函数返回时,相应的帧会从堆栈中清除。以times_two.py程序为例,模块框架位于栈的底部,而times_two()函数框架位于栈的顶部。如果times_two()函数进行函数调用,被调用的函数将位于栈顶。调用栈本身被称为调用栈。

调试视图中的调用栈面板显示了导致当前执行点的整个函数调用链。调用栈面板列出了正在调试的文件和文件中正在运行的行。如果调用经过项目中的其他文件,调用堆栈尤其有用,因为调用堆栈会记录你在调试链中的位置。

此外,如果处于断点处,可以选择调用堆栈中的某一帧,变量面板会显示堆栈中该断点处的程序状态。这对于通过堆栈和生成该值的所有代码追溯错误值的源头非常有用。

逐行查看代码,直到调用times_two()函数。进入函数并注意调用堆栈。现在,times_two()框架已添加到调用堆栈中

调试器完成函数调用并返回总数后,times_two()框架将从调用栈面板中清除。

5.4 条件断点(Logpoints)

你可以配置断点,使其在特定条件为真时触发(条件断点),或在断点被触发一定次数后触发。

当你为断点指定的表达式求值为真时,条件断点就会断开。例如,如果要调试数据库中的数据,可以在出现特定记录时中断。

命中计数使调试器能够执行到指定的出现次数。Python扩展支持的命中计数是前面带有 、>、>=、<、<= 和 % 操作符的整数。参考数据库示例,假设您发现一个错误在进程的第 1500 次迭代时发生。与其每次迭代都进行到第 1500 次,不如设置当 1500 时断开。

要添加条件断点,请右键单击相应行的编辑器边距,然后单击添加条件断点。在出现的下拉菜单中,也可以使用相同的下拉菜单添加命中计数。

5.4 日志点(Logpoints)

日志点会向调试控制台输出一条信息,而不会中断调试器。日志点在编辑器空白处显示为菱形。

要添加日志点,右键单击相应行的编辑器页边空白处,然后单击添加日志点。虽然日志信息是纯文本,但也可以在大括号中加入要评估的表达式。写完信息后按Enter。

5.5 监视(Watch)

当程序只有几个变量时,变量面板可能足以跟踪变量状态。但是,如果程序中有几十个甚至上百个变量,该怎么办呢?如何关注单个变量受所有正在执行的程序的影响就变得很麻烦。

如果你想在不使用变量面板的情况下关注一个(或多个)变量,可以将该变量添加到观察面板。当调试器运行时,观察面板会跟踪所选变量的状态。该面板将表达式作为输入,并在每一行代码执行时更新变量。要在观察面板中添加变量,请单击 "添加表达式 "并输入变量名。或者,也可以在编辑器中突出显示变量,右键单击,然后单击添加到观察。

试试看 在编辑器中打开watch.py,在greeting = ' Hello World'处设置断点,然后启动调试器。调试器在断点处暂停后,将变量total添加到 Watch 面板。

查看代码的每一行,注意变量赋值在代码执行过程中的变化。

注意 当调试器启动时,total 的值反映 NameError: name 'total' is not defined(名称'total'未定义)。出现这种情况的原因是程序尚未执行定义变量的代码行。一旦程序执行 total = 0,变量值就会更新为 0。 如果同一变量名在调用栈的不同位置使用(例如不同作用域),则以最新的变量名为准。当帧退出并从堆栈中移除时,Watch 面板会显示下一个最高作用域中的变量值。

watch.py 中有六个变量:greeting、total、iteration、numbers、num 和 iteration_num。当调试器走过程序的每一行时,列表就会增加,以包括执行的每个变量。当调试器走过每一行代码时,你可以通过查看观察面板更好地关注总计的状态。

5.6 调试控制台(The Debug Console)

调试程序时,您可以在调试控制台中尝试潜在的错误修复方法,而不是修改代码后重新启动。通过调试控制台,您可以在程序当前状态下尝试代码,而无需停止调试器。您可以在调试控制台中尝试不同的方案,并在调试器暂停时将修正结果复制到程序中。

调试控制台在编辑器中提供了Python读取-评估-打印-循环 (REPL Read-Eval-Print-Loop) 功能。通过调试控制台,您可以访问和修改程序的所有变量,调用函数,求值表达式,以及使用程序的当前状态运行任何您喜欢的代码。控制台中的任何操作都会影响程序的当前状态。此外,调试控制台输入支持语法着色、缩进、自动关闭引号以及活动编辑器模式的其他语言特性。

您可以通过以下三种方式访问调试控制台:

  • 运行视图: 单击调试控制台图标。

  • 键盘快捷键: 按 Cmd/Ctrl+Shift+Y。

  • 主菜单: 查看 -> 调试控制台。

调试控制台会在您输入时显示建议。按Enter键后,表达式将被评估。要输入多行,请在两行之间按下Shift+Enter 键。

在调试控制台中,可以直接调用函数并评估结果。如果调用的函数有断点,则可以逐步查看函数代码。退出函数后,你仍然处于与之前相同的程序状态。你还可以使用调试控制台更改变量,运行程序中没有的代码。

试用:在编辑器中打开Fibonacci_generator.py文件。Fibonacci_generator.py文件包含生成斐波那契数字列表的程序。斐波那契数构成一个序列,序列中的下一个数字是序列中前两个数字之和(例如 1、1、2、3、5、8、13、21)。程序启动时,会提示用户输入程序应生成的数字总数。为了演示调试控制台,故意在程序中添加了一个错误。请按照以下说明使用调试控制台修复错误:

  • 在终端运行程序,根据提示输入1作为要生成的斐波纳契数。程序成功运行并返回 [1]。
  • 再次在终端运行程序,根据提示输入2作为要生成的斐波那契数。程序成功运行并返回 [1,2]。
  • 再次在终端运行程序,根据提示输入要生成的斐波那契数 3。这次程序运行时停滞了。在键盘上按 Ctrl+C 退出程序。退出程序后,终端会出现一个错误:
How many Fibonacci numbers would you like to generate? 3
^CTraceback (most recent call last):
  File "/home/andrew/code/Code_Samples/Code Samples/debugger/Fibonacci_generator.py", line 18, in <module>
    print(gen_fib())
  File "/home/andrew/code/Code_Samples/Code Samples/debugger/Fibonacci_generator.py", line 12, in gen_fib
    while i < (count - 1):
KeyboardInterrupt

在计数 > 2 时的 elif 语句中,i == 1 似乎有问题。可以认为程序中的错误就出在这里。

  • 在i == 1处设置断点并启动调试器。出现提示时,输入3。

调试器在断点处暂停后,查看变量面板,确认变量是否反映了适当的值。

  • Step over到 while 循环的最后一行

注意到尽管已经生成了所需的斐波纳契数,循环仍开始了另一次迭代。继续跳过 while 循环,程序会生成相同的斐波纳契数,而且 while 循环从未中断过。该程序是一个无限循环。

  • 启动调试器并打开调试控制台

调试器在断点处停止后,输入i+=1 以递增 i 的值。
在变量面板中,i 的值从 1 变为 2。 现在,继续调试程序时,执行代码的其余部分,只生成三个斐波那契数字。

5.7 启动配置

启动配置可让你配置不同调试会话的运行方式,并将这些配置持久保存在 launch.json 文件中。launch.json 文件保存在项目根目录下的 .vscode 文件夹中,也可在用户或工作区设置中访问。要进行调试,launch.json 文件中至少需要一个配置。

要创建 launch.json 文件,请在运行视图中单击创建 launch.json 文件。或者,你也可以从 "运行 "菜单中选择 "运行" ➪ "打开配置 "来创建 launch.json 文件。

Visual Studio Code会从命令面板打开配置菜单,提示您选择默认配置作为新配置的启动模板。

Python 扩展提供了以下默认配置:

  • Python 文件-调试当前活动的 Python 文件。
  • 模块-通过使用 -m 调用 Python 模块来调试该模块。
  • 远程连接-为调试服务器监听提供主机名和端口号。
  • 使用进程 ID 附加-当运行在 Visual Studio Code 之外启动的 Python 脚本时,将调试器附加到非调试模式下的 Python 进程。附加到进程需要进程 ID。

该扩展还为网络应用程序提供了三种默认配置:

  • Django
  • Flask
  • Pyramid

要了解有关调试Django应用程序的更多信息,请参阅代码.visualstudio.com/docs/python/tutorial-django#_explore-the-debugger 的Django教程。要了解有关调试 Flask 应用程序的更多信息,请参阅位于 code.visualstudio.com/docs/python/tutorial-flask#_run-the-app-in-the-debugger 的 Flask 教程。

选择启动模板后,launch.json文件将添加到 .vscode 文件夹中,并在编辑器中打开。

编辑 launch.json 文件时,IntelliSense 会提示(Ctrl+空格键)可用属性列表。您也可以对文件中的所有属性使用悬停帮助。由于不同语言的属性可能不同,使用悬停帮助可以了解有关属性的更多信息。

launch.json 文件可以包含任意数量的配置。要添加配置,请从运行菜单或 launch.json 编辑器中单击添加配置。

这里提供了 launch.json 文件可用的核心设置:

  • name -为出现在 Visual Studio 代码下拉列表中的调试配置提供名称。
  • type-指定要使用的调试器类型;对于 Python 代码,将其设置为 python。
  • request-指定开始调试的模式:
    • launch: 在程序中指定的文件上启动调试器。
    • attach(附加): 将调试器附加到远程服务器上已经运行的进程上,该进程不能随意重启。本地需要与程序中指定的相同的源代码文件。
  • program-提供 Python 程序入口模块(启动文件)的完整路径。默认配置中经常使用的 ${file} 值,会使用编辑器中的当前活动文件。通过指定启动文件,可以确保无论打开哪个文件,都能以相同的入口点启动程序。
  • python- 指向用于调试的 Python 解释器的完整路径。如果未指定,则默认使用 python.pythonPath 设置中指定的解释器,相当于使用 ${config:python.pythonPath} 值。要使用其他解释器,请在调试配置的 python 属性中指定其路径。
  • args-指定传递给程序的参数。参数字符串中以空格分隔的每个元素都应包含在引号中。
  • cwd-指定调试器的当前工作目录,它是代码中使用的任何相对路径的基本文件夹。如果省略,当前工作目录默认为${workspaceFolder},即编辑器中打开的文件夹。或者,也可以使用在每个平台上定义的自定义环境变量,其中包含要使用的 Python 解释器的完整路径,这样就不需要额外的文件夹路径了。

其他配置请访问 code.visualstudio.com/docs/python/debugging#_set-configuration-options。还有一些非Python 特有的附加属性,可以在 launch.json 中设置。要了解更多信息,请参阅 code.visualstudio.com/docs/editor/debugging#_launchjson-attributes。

posted @ 2024-04-12 19:00  磁石空杯  阅读(263)  评论(0编辑  收藏  举报