理解 Python 中的 Traceback

当 Python 程序中发生异常时,通常会打印出 traceback。了解如何阅读 traceback 可以帮助您轻松识别错误并进行修复。在本教程中,我们将了解 traceback 能告诉我们什么。

完成本教程后,您将了解:

  • 如何阅读 traceback
  • 如何在没有异常的情况下打印调用堆栈
  • traceback 中未显示的内容

通过我的新书 Python for Machine Learning 快速启动您的项目,其中包含分步教程和所有示例的Python 源代码文件。

让我们开始吧。

理解 Python 中的 Traceback
照片作者:Marten Bjork,部分权利保留

教程概述

本教程分为四个部分;它们是

  1. 简单程序的调用层次结构
  2. 异常时的 traceback
  3. 手动触发 traceback
  4. 模型训练中的一个示例

简单程序的调用层次结构

让我们看一个简单的程序

这个程序将打印出缩进的 Python 字典 data。它的输出如下:

这是一个简短的程序,但函数之间相互调用。如果我们为每个函数开头添加一行,我们可以通过控制流揭示输出是如何产生的。

输出会因更多信息而变得混乱。

现在我们知道了每个函数被调用的顺序。这就是调用堆栈的概念。在任何时间点,当我们运行函数中的一行代码时,我们想知道是什么调用了该函数。

异常时的 traceback

如果我们像下面这样在代码中输入一个错别字

错别字在最后一行,闭括号应该在行的末尾,而不是在任何 + 之前。print() 函数的返回值是 Python 的 None 对象。将任何东西添加到 None 会触发一个异常。

如果您使用 Python 解释器运行此程序,您会看到这个:

以“Traceback (most recent call last):”开头的行是 traceback。它是程序遇到异常时程序的堆栈。在上面的示例中,traceback 是“最近调用的最后”顺序。因此,您的主函数在顶部,而触发异常的函数在底部。所以我们知道问题出在 printdict() 函数内部。

通常,您会在 traceback 的末尾看到错误消息。在此示例中,它是 TypeError,由将 None 和字符串相加触发。但 traceback 的帮助到此为止。您需要找出哪个是 None,哪个是字符串。通过阅读 traceback,我们还知道触发异常的函数 printdict() 是由 indentprint() 调用的,而 indentprint() 又是由 printlist() 调用的,依此类推。

如果您在 Jupyter notebook 中运行,输出如下:

这些信息基本相同,但它提供了每个函数调用之前和之后的所有行。

想开始学习机器学习 Python 吗?

立即参加我为期7天的免费电子邮件速成课程(附示例代码)。

点击注册,同时获得该课程的免费PDF电子书版本。

手动触发 traceback

打印 traceback 最简单的方法是添加一个 raise 语句来手动创建一个异常。但这也会终止您的程序。如果我们想在任何时候打印堆栈,即使没有任何异常,我们也可以这样做:

traceback.print_stack() 这行代码将打印当前的调用堆栈。

但事实上,我们通常只在出错时才想打印 traceback(以便我们了解出错的原因)。更常见的用例是以下:

这是重复计算函数(例如蒙特卡洛模拟)的典型模式。但如果我们不够小心,我们可能会遇到一些错误,例如在上面的示例中,我们可能会遇到除以零的情况。问题在于,在更复杂的计算中,您无法轻易发现缺陷。就像上面一样,问题隐藏在对 compute() 的调用中。因此,了解我们如何获得错误很有帮助。但同时,我们希望处理错误情况,而不是让整个程序终止。如果我们使用 try-catch 结构,traceback 默认不会被打印。因此,我们需要手动使用 traceback.print_exc() 语句来完成。

实际上,我们可以让 traceback 更详尽。因为 traceback 是调用堆栈,我们可以检查调用堆栈中的每个函数并检查每个级别的变量。在复杂的案例中,这是我通常用于进行更详细跟踪的函数:

模型训练示例

traceback 中报告的调用堆栈有一个限制:您只能看到 Python 函数。对于您编写的程序来说,这通常没问题,但许多大型 Python 库的部分是用其他语言编写并编译成二进制文件的。例如 Tensorflow。为了性能,所有底层操作都是二进制的。因此,如果您运行以下代码,您会看到一些不同的东西:

模型中第一个 LSTM 层的 input_shape 参数应为 (n_in, 1) 以匹配输入数据,而不是 (n_in+1, 1)。运行最后一行时,此代码将打印以下错误:

如果查看堆栈跟踪,您将无法看到完整的调用堆栈。例如,您知道顶层调用了 model.fit(),但第二层来自一个名为 error_handler() 的函数。在这里,您看不到 fit() 函数是如何触发它的。这是因为 TensorFlow 经过了高度优化。很多东西都隐藏在编译后的代码中,Python 解释器无法看到。

在这种情况下,耐心地阅读堆栈跟踪并找到原因的线索至关重要。当然,错误消息通常也会为您提供一些有用的提示。

进一步阅读

如果您想深入了解,本节提供了更多关于该主题的资源。

书籍

Python 官方文档

总结

在本教程中,您学习了如何读取和打印 Python 程序中的堆栈跟踪。

具体来说,你学到了:

  • 堆栈跟踪告诉您哪些信息
  • 如何在不引发异常的情况下在程序的任何点打印堆栈跟踪

在下一篇文章中,我们将学习如何在 Python 调试器中导航调用堆栈。

掌握机器学习 Python!

Python For Machine Learning

更自信地用 Python 编写代码

...从学习实用的 Python 技巧开始

在我的新电子书中探索如何实现
用于机器学习的 Python

它提供自学教程数百个可运行的代码,为您提供包括以下技能:
调试性能分析鸭子类型装饰器部署等等...

向您展示高级 Python 工具箱,用于
您的项目


查看内容

4 条回复:“理解 Python 中的堆栈跟踪”

  1. Manza Zityab 2021 年 12 月 31 日下午 4:09 #

    非常感谢。我将尝试练习这个 bash 脚本。如果遇到问题,我会向您提问的,亲爱的。

    • James Carmichael 2022 年 1 月 1 日下午 12:11 #

      不客气,Manza!继续努力!

  2. Yuval 2023 年 3 月 5 日下午 7:07 #

    感谢您的这篇文章。非常有帮助。
    一条评论
    我认为 print_tb_with_local() 的第 10 行有一个拼写错误
    应该是

    tb = tb.tb_next

    (没有括号)。

    祝好,
    Yuval

    • James Carmichael 2023 年 3 月 6 日上午 11:25 #

      感谢 Yuval 的反馈!

留下回复

Machine Learning Mastery 是 Guiding Tech Media 的一部分,Guiding Tech Media 是一家领先的数字媒体出版商,专注于帮助人们了解技术。访问我们的公司网站以了解更多关于我们的使命和团队的信息。