Python 中的 Traceback

2024年8月29日 | 13分钟阅读

Python Traceback 简介

当 Python 代码中引发异常时,Python 会返回一个 Traceback。初次看到或不了解其含义时,**Traceback** 的输出可能令人费解。然而,Python 编程语言中的 Traceback 包含了大量数据,可以帮助我们诊断和修复代码中引发异常的原因。理解 Python traceback 提供的数据对于成为一名更好的 Python 程序员至关重要。

在下面的教程中,我们将讨论 Python 编程语言中的 Traceback。但在教程结束时,我们将能够识别一些最常见的 traceback。

那么,让我们开始吧。

理解 Python 编程语言中的 Traceback

**Traceback** 是一个报告,其中包含在特定时间点代码行上对函数的调用。Traceback 有多种名称,例如 **stack trace、stack traceback、backtrace** 等等。然而,在 Python 编程语言中,我们使用“Traceback”一词。

每当程序引发异常时,Python 都会返回当前的 Traceback,以帮助我们了解出了什么问题。让我们看下面的示例来说明一种这种情况。

示例

输出

Traceback (most recent call last):
  File "D:\Python\pytrace.py", line 10, in 
    welcome( "James" )
  File "D:\Python\pytrace.py", line 6, in welcome
    print( "Hello, " + nam ) # using 'nam' instead of 'name'
NameError: name 'nam' is not defined

说明

在上面的代码片段中,我们定义了一个名为 **welcome** 的自定义函数,该函数接受一个名为 **“name”** 的参数。但是,在函数内打印一些消息时,我们将 **“name”** 参数拼写错误为 **“nam”**。结果,在调用函数时引发异常时,Python 打印了一个 traceback 消息。

正如我们在输出中观察到的,traceback 消息包含了诊断问题所需的所有信息。traceback 消息的最后一行表示引发的异常类型,以及与该异常相关的适当数据。traceback 消息的早期行指示了导致异常的代码。

在上面的 traceback 中,异常是 **NameError**,这意味着引用了未定义的名称(例如变量、类、函数)。在本例中,引用的名称是 **“nam”**。

在这种情况下,最后一行包含足够的信息来帮助我们解决问题。搜索代码中拼写错误的名称 **nam**,它将正确地指向我们。通常,代码会复杂得多。

阅读 Python 中的 Traceback

Python 中的 Traceback 包含大量关于代码行中引发的异常的宝贵数据。在下一节中,我们将了解如何阅读不同的 traceback 以了解 traceback 中存储的各种数据。

Python traceback 分为不同的部分。每个部分都有其重要性。让我们看下面的 traceback

Traceback

在 Python 编程语言中,从下往上阅读 traceback 消息是一个好习惯。现在,让我们详细了解上面的 traceback。

  1. 蓝色块:最后一行用蓝色突出显示,表示错误消息行。此行包含引发异常的名称。
  2. 绿色块:异常名称之后是与错误相关的消息。此消息通常包含有价值的数据,可帮助我们理解引发异常的原因。
  3. 黄色块:黄色块包含从下往上(最近到最远)的函数调用。这些调用用每对两行的条目表示。每对调用的第一行包含文件名、行号和模块名等数据,这些数据都指示了代码的位置。
  4. 粗体行:这些粗体行是这些调用的第二行,其中包含实际处理的代码片段。

当在命令行和 REPL 中执行代码时,traceback 的输出会有些不同。让我们以在 REPL 中执行相同的示例并理解 traceback 输出。

REPL

正如我们在上面的 REPL 代码片段中观察到的,traceback 消息在文件名处返回“****”,因为我们通过标准输入输入了代码。此外,traceback 消息中未显示执行的代码行。

注意:如果有些人喜欢查看不同编程语言中的堆栈跟踪,那么与 Python 编程语言中的 traceback 相比,它的外观会有很大的不同。大多数语言将异常显示在顶部,然后从上到下(最近的调用到最远的调用)。

而在 Python 中,traceback 应从下往上阅读。这非常有用,因为当返回 traceback 时,终端通常会停在输出的底部,为我们提供了开始阅读 traceback 的理想位置。

理解 Python 中的一些常见 Traceback

一旦我们理解了在引发异常时如何在 Python 中阅读 Traceback,让我们来了解一些在编码时可能看到的常见 traceback。

以下是一些我们可能遇到的标准异常,以及它们的含义、引发原因以及我们可以在其 traceback 中找到的数据。

AttributeError

当尝试访问对象上不存在该已定义属性的属性时,会引发 **AttributeError** 异常。Python 文档描述了何时会引发 **AttributeError** 异常。

当属性的引用或赋值失败时,将引发此异常。

让我们看下面的示例,其中引发了 **AttributeError** 异常。

示例

输出

Traceback (most recent call last):
  File "D:\Python\pytrace.py", line 2, in 
    print(my_int.an_attribute)
AttributeError: 'int' object has no attribute 'an_attribute'

说明

在上面的代码片段中,我们定义了一个整数并尝试访问其属性。但是,当我们执行程序时,它引发了 **AttributeError** 异常,并指出特定的对象类型,在本例中为 **int**,没有访问的属性,即 **an_attribute**。查看错误消息行中的 **AttributeError** 异常可以方便地帮助我们识别我们试图访问的属性以及如何修复它。

通常,每当引发此类异常时,它都表示我们可能正在处理一个实例,而该实例不是我们想要的类型。

让我们看另一个示例以更好地说明

示例

输出

Traceback (most recent call last):
  File "D:\Python\pytrace.py", line 5, in 
    my_list.append( 30 )
AttributeError: 'tuple' object has no attribute 'append'

说明

在上面的代码片段中,我们定义了一个列表,并使用 **append()** 方法添加另一个元素到列表中。但是,结果是,我们可能期望 **my_list** 是 **list** 类型,它包含一个名为 **append()** 的方法。当我们收到 **AttributeError** 异常时,我们观察到它是在调用 **append()** 函数时引发的,这表明我们可能没有处理我们想要的那个对象类型。

通常,这发生在当我们期望从方法或函数调用返回的对象是特定类型时,但最终我们得到了 None 类型对象。在上述情况下,错误消息行将显示 **AttributeError: 'NoneType' object has no attribute 'append'**。

ImportError

当 **import** 语句出现问题时,会引发 **ImportError** 异常。如果找不到我们试图导入的模块或库,或者从库或模块中导入的内容不存在,我们将收到此异常,或其子类 **ModuleNotFoundError**。Python 文档指出了何时会引发 **ImportError** 异常。

当 **import** 语句难以加载库或模块时,将引发此异常。此外,当 **from ... import** 中的 **'from list'** 包含一个找不到的名称时,也会引发此异常。

让我们看一个演示如何引发 **ImportError** 和 **ModuleNotFoundError** 的示例。

示例

输出

# Output for the first line
Traceback (most recent call last):
  File "D:\Python\pytrace.py", line 2, in 
    import xyz
ModuleNotFoundError: No module named 'xyz'
# Output for the second line
Traceback (most recent call last):
  File "D:\Python\pytrace.py", line 3, in 
    from collections import xyz
ImportError: cannot import name 'xyz' from 'collections' (D:\Python39\lib\collections\__init__.py)

说明

在上面的代码片段中,我们试图导入一个不存在的库或模块 **xyz**,这导致了 **ModuleNotFoundError** 异常。另一方面,当我们尝试从存在的 collections 库导入不存在的模块 **xyz** 时,程序引发了 **ImportError** 异常。traceback 底部的错误消息行显示了哪个特定项无法导入,在这两种情况下,都是 **xyz**。

IndexError

当尝试从序列(如元组或列表)中检索索引,但在序列中找不到该索引时,通常会引发 **IndexError** 异常。Python 文档指出了何时会引发 Index 异常。

当序列的下标越界时,将引发此异常。

让我们看下面的示例,演示如何引发 **IndexError** 异常。

示例

输出

Traceback (most recent call last):
  File "D:\Python\pytrace.py", line 5, in 
    print( my_list[ 4 ] )
IndexError: list index out of range

说明

在上面的代码片段中,我们定义了一个包含四个元素的列表 **my_list**。但是,当我们试图打印索引号为 **5** 的元素时,程序引发了 **IndexError** 异常。**IndexError** 异常的错误消息没有提供充分的信息。我们可以观察到我们有一个序列引用,即 **out of range**,以及序列的类型,在这种情况下是列表。结合 traceback 的其余部分,这些数据通常足以帮助我们快速识别如何解决问题。

KeyError

**KeyError** 异常与 **IndexError** 异常类似,当尝试访问映射(通常在字典等数据结构中)中不存在的键时会引发此异常。Python 文档指出了何时会引发 **KeyError** 异常。

当字典(映射)键在现有键集中找不到时,将引发此异常。

让我们看下面的示例来理解如何引发 **KeyError** 异常。

示例

输出

Traceback (most recent call last):
  File "D:\Python\pytrace.py", line 5, in 
    print( mydict['Sam'] )
KeyError: 'Sam'

说明

在上面的代码片段中,我们定义了一个包含一些键值对的 **dictionary**。然后我们试图访问字典中不存在的 **key** 的 **value**。结果,程序引发了 **KeyError** 异常,并指出我们正在寻找的 **key** 无法找到。

NameError

当我们在代码行中引用了未定义的变量、类、函数、模块或其他名称时,会引发 **NameError** 异常。Python 文档指出了何时会引发 **NameError** 异常。

当找不到局部或全局名称时,将引发此异常。

让我们看下面的示例以理解如何引发 **NameError** 异常。

示例

输出

Traceback (most recent call last):
  File "D:\Python\pytrace.py", line 6, in 
    myself( "Robin" )
  File "D:\Python\pytrace.py", line 3, in myself
    print("My name is", nam)
NameError: name 'nam' is not defined

说明

在上面的示例中,我们定义了一个名为 **myself()** 的函数,它接受一个名为 **name** 的参数。但是,我们在打印一些语句时将名称拼写错误为 **nam**。然后我们调用了该函数。结果,程序引发了 **NameError** 异常,因为名称 **'nam'** 在程序中未定义。

SyntaxError

当 Python 程序的语法不正确时,通常会引发 **SyntaxError** 异常。Python 文档指出了何时会引发 **SyntaxError** 异常。

当解析器遇到 Python 语法错误时,将引发此异常。

让我们看一个说明如何引发 **SyntaxError** 异常的示例。

示例

输出

File "D:\Python\pytrace.py", line 2
    def myself( name )
                      ^
SyntaxError: invalid syntax

说明

在上面的语法中,我们定义了一个名为 **myself()** 的函数,但忘记在函数定义后包含冒号“**:**”。结果,当我们执行该函数时,程序引发了 **SyntaxError** 异常,并指出了程序语法的问题。代码行下方的 ^(插入符号)标记表示问题的位置。

此外,我们可以观察到 **SyntaxError** traceback 消息不显示常规的第一行“**Traceback (most recent call last):**”。这是因为 **SyntaxError** 异常是在 Python 尝试分析代码行时引发的,并且代码行不是以字面方式处理的。

TypeError

当语法尝试对无法执行该函数的实例执行某些函数时,会引发 **TypeError** 异常,例如尝试将整数添加到字符串,或者对未指定长度的对象调用 **len()** 函数。Python 文档指出了何时会引发 **TypeError** 异常。

当函数或操作应用于错误类型的对象时,将引发此异常。

让我们看下面的示例,演示如何引发 **TypeError** 异常。

示例

输出

Traceback (most recent call last):
  File "D:\Python\pytrace.py", line 4, in 
    myadd = myint + mystr
TypeError: unsupported operand type(s) for +: 'int' and 'str'

说明

在上面的示例中,我们定义了两个变量,一个为 **整数**,一个为 **字符串**。然后我们对这些变量执行加法运算并尝试打印结果。但是,程序返回了 **TypeError** 异常,因为我们试图将 **整数** 值与 **字符串** 值相加。

同样,当我们在 **'int'** 数据类型上使用 **len()** 函数时,也会引发此异常。

让我们看下面的示例来演示这一点。

示例

输出

Traceback (most recent call last):
  File "D:\Python\pytrace.py", line 5, in 
    print("Length:", len(myint))
TypeError: object of type 'int' has no len()

说明

在上面的示例中,我们定义了一个 **'int'** 数据类型的变量,并尝试对该变量执行 **len()** 函数。但是,程序引发了 **TypeError**,并指出我们不能对 **'int'** 数据类型的对象执行 **len()** 函数。

ValueError

当对象的值不正确时,会引发 **ValueError** 异常。此异常与 **IndexError** 异常类似,因为在 **IndexError** 异常的情况下,索引值超出了序列的范围。相比之下,**ValueError** 异常适用于更通用的场景。Python 文档指出了何时会引发 **ValueError** 异常。

当函数或操作接收到类型正确的参数;但是,值不合适且状态没有由 **IndexError** 等更具体的异常定义时,将引发此异常。

让我们看一个基于 **ValueError** 异常的示例。

示例

输出

Traceback (most recent call last):
  File "D:\Python\pytrace.py", line 2, in 
    var1, var2, var3 = [10, 20, 30, 40]
ValueError: too many values to unpack (expected 3)

说明

在上面的示例中,我们试图解包四个值,但只得到了三个。因此,程序引发了 **ValueError** 异常。

让我们看另一个基于 **ValueError** 异常的示例。

示例

输出

Traceback (most recent call last):
  File "D:\Python\pytrace.py", line 2, in 
    var1, var2, var3, var4 = [10, 20, 30]
ValueError: not enough values to unpack (expected 4, got 3)

说明

在上面的语法中,我们试图解包太多的值。结果,程序返回了 **ValueError** 异常,并指出没有足够的值可以解包(预期 4 个,得到 3 个)。