当代码中抛出异常时,Python会打印一个回溯。如果是第一次看到回溯输出,或者不知道它在告诉你什么,那么可能会让你无所适从。但是Python回溯信息丰富,可以帮助你诊断和修复代码中异常的原因。理解Python回溯提供的信息对于成为一名更好的Python程序员来说非常重要。
本教程结束时,您将能够:
理解你下一次遇到的回溯识别一些很常见的回溯在处理异常的同时成功记录回溯结尾有小编为大家准备的免费学习资料。
什么是Python回溯?
回溯是一个包含在代码定点执行的函数调用的报告。回溯有许多名称,包括堆栈跟踪、堆栈跟踪、向后跟踪,可能还有其他名称。在Python中,使用的术语是回溯。
当你的程序抛出一个异常时,Python会打印出当前的回溯信息来帮助你知道哪里出错了。这里有一个例子来说明这种情况:
这里,我们用参数someone调用greet()。但是,在greet()中,不使用这个变量名。相反,在print()调用中,它被拼错为someon。
注意:本教程假设您理解Python异常。如果你不熟悉或者只是想复习一下,那么你应该查一下Python Exception: Introduction。
当你运行这个程序时,你会得到如下回溯:
这个回顾性输出包含诊断问题所需的所有信息。backtrace输出的最后一行告诉您抛出了什么类型的异常,以及关于这个异常的一些相关信息。回溯的前几行表示抛出异常的代码。
在上面的回溯中,异常是NameError,这意味着存在对未定义名称(变量、函数、类)的引用。在这种情况下,引用的名称是someon。
这个例子中的最后一行有足够的信息来帮助你解决这个问题。在代码中搜索someon这个名字(这是一个拼写错误)将为您指出正确的方向。但是,您的代码通常比这个示例复杂得多。
如何阅读Python回溯?
当您试图确定代码中异常的原因时,Python回溯包含许多有用的信息。在本节中,您将浏览不同的回溯,以了解回溯中包含的不同信息。
Python回溯概述
每个Python回溯都有几个重要部分。下图突出显示了每个部分:
在Python中,最好从下往上阅读回溯:
蓝色框: 回溯的最后一行是错误消息行。它包含捕获的异常名称。绿色框: 异常名称之后是错误消息。此消息通常包含有助于理解引发异常的原因的信息。黄色框: 在回溯的上端,各种函数调用从底部移动到顶部,从最近的函数调用到最远的函数调用。这些调用的每一个调用都由两行条目表示。每个调用的第一行包含文件名、行号和模块名等信息,这些信息都指定了代码的位置。红色下划线: 这些调用的第二行包含实际被执行的代码。
当您在命令行上执行代码并在REPL运行代码时,回溯输出会有一点不同。下面是在REPL执行的与上一节相同的代码,以及执行后生成的回溯输出:
注意使用“其中文件名被替换。这是有意义的,因为您通过标准输入来输入代码。此外,已执行的代码行不会显示在回溯中。
注意:如果你经常在其他编程语言中查看回溯,你会注意到它和Python的回溯方法有一个主要的区别。大多数其他语言在顶部打印异常,然后从上到下,从最近的调用到最远的调用。
我以前说过,但我想在这里重申,你应该从底部到顶部阅读Python回溯。这非常有用,因为回溯会被打印出来,你的终端(或者你正在读取回溯的任何地方)通常会在输出的底部结束,这为你提供了开始读取回溯的最佳位置。
特定回顾浏览
浏览一些具体的回溯输出,可以帮助你更好的理解,看看回溯会给你提供什么信息。
以下代码在一个示例中用于说明Python回溯提供的信息:
这里,who_to_greet()接受一个值person并返回它,或者提示返回一个值。
然后,greet()接受一个问候语名称someone和一个可选的问候语值,并将someone值传入以调用print()。问候谁()。
最后,greet_many()将遍历人员列表并调用greet()。如果在调用greet()时抛出异常,则打印一个简单的备份问候语。
只要您提供正确的输入,这段代码中就没有会抛出异常的错误。
如果在greetings.py的底部添加一个greeting()调用,然后指定一个它无法预测的关键字参数(例如greeting (‘Chad ‘,greting=’Yo ‘)),就会迎来下面的回溯:
类似地,对于Python回溯来说,最好是向后处理它并将输出上移。从回溯的最后一行开始,您可以看到该异常是一个TypeError。异常类型后的消息(冒号后的所有内容)为您提供了一些有用的信息。它告诉您greet()是用一个意外的关键字参数调用的,并给出了未知参数的名称:greting。
继续向上移动,您可以看到导致异常的那一行。在本例中,这一行是我们在greetings.py底部添加的greet()调用。
下一行给出了代码所在文件的路径、代码所在文件的行号以及代码所在的模块。在本例中,因为我们的代码不使用任何其他Python模块,所以我们将只看到
使用不同的文件和不同的输入,您可以看到回溯实际上会为您指出找到问题的正确方向。如果您正在跟踪,请从greetings.py底部删除有问题的greeting()调用,并将以下文件添加到您的目录中:
这里,您已经设置了另一个Python文件,该文件将导入前面的模块greetings.py并从中使用greet()。以下是运行example.py时发生的情况:
本例中捕获的异常也是一个TypeError,但是这次该消息没有那么有用。它告诉你在代码的某个地方,它期望使用一个字符串,但是传入的是一个整数。
向上移动,您可以看到正在执行的代码行。然后是文件和代码的行号。然而,我们这次得到的不是
转到要执行的下一行代码,我们看到有一个可疑的对greet()的调用,它是一个整数。
有时在抛出一个异常后,另一段代码会捕捉到该异常并引发另一个异常。在这种情况下,Python将按照收到异常的顺序输出所有异常回溯,并以抛出的最后一个异常的回溯结束。
这可能有点混乱。这里有一个例子。在greetings.py底部添加对greet_many()的调用
这将打印出对所有三个人的问候。但是,如果您运行此代码,您将看到一个输出多个回溯的示例:
请注意上面输出中突出显示的行,它们在处理过程中以开始。在所有回溯之间,你会看到这条线。它的信息非常明确。当您的代码试图处理前一个异常时,会引发另一个异常。
注:Python之前显示异常回溯的特性是在Python 3中加入的。在Python2中,你只会得到最后一个异常回溯。
你以前见过前面的异常,就在你用整数调用greet()的时候。因为我们在要问候的人的列表中添加了1,所以我们可以期待相同的结果。但是,函数greet_many()将对greet()的调用封装在一个try and except块中。这样,当greet()抛出异常时,greet_many()会打印一个默认的问候语。
greetings.py的相关部分在此重复:
因此,当greet()由于错误的整数输入导致TypeError时,greet_many()将处理该异常并尝试打印一个简单的问候语。这里的代码最终会导致另一个类似的异常。它仍然尝试添加一个字符串和一个整数。
查看所有回溯输出可以帮助您理解异常的真正原因。有时候,当你看到最后一个异常被抛出,导致回溯,你还是看不出哪里出错了。在这些情况下,移到异常的前面通常会让您更好地理解根本原因。
Python中常见的回溯有哪些?
编程时,知道程序抛出异常时如何读取Python回溯可能非常有用,但知道一些比较常见的回溯也能提高编程过程。
以下是您可能会遇到的一些常见异常,它们被触发的原因及其含义,以及您可以在它们的回溯中找到的信息。
属性错误
当您试图访问对象上未定义的属性时,会引发AttributeError。Python文档定义了何时抛出该异常:
当属性引用或赋值失败时抛出。
以下是引发AttributeError的示例:
AttributeError的错误消息行告诉您,在这种情况下,特定的对象类型(在这种情况下是int)没有访问an_attribute属性。在错误消息行中看到AttributeError可以帮助您快速确定您正在尝试访问哪个属性以及在哪里修复它。
在大多数情况下,获得此异常表明您正在处理的对象可能不是您期望的类型:
在上面的例子中,您可能期望_list是list类型,它有一个名为。追加()。当您收到AttributeError异常并看到它是在您尝试调用。append(),这意味着您正在处理的对象类型可能不是您所期望的。
通常,当您希望从函数或方法调用中返回一个特定类型的对象时,会发生这种情况,并且您最终会得到一个None类型的对象。在此示例中,错误消息将被写入attribute error:“None type”对象没有属性“append”。
导入错误
当导入语句出错时,将抛出ImportError。如果找不到您试图导入的模块,或者如果您试图从一个模块中导入该模块中不存在的内容,您将会得到这个异常,或者它的子类ModuleNotFoundError。Python文档定义了何时抛出该异常:
当导入语句在尝试加载模块时遇到困难时抛出。当在from…导入的“from list”中有一个找不到的名称时,也会引发该错误。
以下是被触发的ImportError和ModuleNotFoundError的示例。
在上面的例子中,你可以看到当我们试图导入不存在的模块asdf时,会导致ModuleNotFoundError。当试图从现有模块(在本例中是集合)导入不存在的asdf时,将导致ImportError。追溯到底部的错误消息行,告诉您在这两种情况下都不能导入asdf。
索引错误
当您尝试从序列(如列表或元组)中检索索引时,如果在该序列中找不到该索引,将会引发IndexError。Python文档定义了何时抛出该异常:
当序列的下标超出范围时引发。
以下是提高IndexError的示例:
IndexError的错误信息行不会给你好的信息。您可以看到有一个超出范围的序列引用和该序列的类型,在本例中是一个列表。这些信息以及其他回溯信息通常足以帮助您快速确定如何修复此问题。
键盘错误
与IndexError类似,当您试图访问一个不在映射中的键(通常是一个dict)时,会引发一个KeyError。你可以把它想成一个IndexError,只适用于字典。Python文档定义了何时抛出该异常:
在现有键集中找不到映射(字典)键时引发。
以下是被触发的KeyError的示例:
KeyError的错误消息行给出了找不到的键。这个内容不多,但是结合其他回溯内容,通常就足够修复这个问题了。
要了解更多关于KeyError的信息,请参阅Python KeyError异常和如何处理它们。
名称错误
当您引用代码中未定义的变量、模块、类、函数或其他名称时,将会引发NameError。Python文档定义了何时抛出该异常:
找不到本地或全局名称时引发。
在下面的代码中,greet()接受一个参数person。但是在函数本身中,这个参数被拼错为persn:
NameError跟踪的错误消息行给出了缺少的名称。在上面的例子中,传递给函数的是一个拼写错误的变量或参数。
如果是拼错的参数,也会抛出NameError:
你似乎没做错什么。回溯中执行和引用的最后一行看起来不错。如果您发现自己处于这种情况,您所要做的就是查看代码并确定person变量的使用和定义位置。在这里,您可以很快看到参数名拼写错误。
语法错误
当代码中存在不正确的Python语法时,将触发SyntaxError。Python文档定义了何时抛出该异常:
当分析器产生语法错误时抛出。
以下代码的问题是在函数定义行的末尾缺少一个冒号。在Python REPL中,按下Enter键后会立即引发此语法错误:
SyntaxError的错误信息行只告诉你代码的语法有问题。看上面一行得到问题所在的行,一般用一个(脱字符号)指向问题点。这里,函数的def语句中缺少冒号。
类似地,使用SyntaxError回溯,传统的第一行回溯(最近一次调用last:)也会丢失。这是因为当Python试图解析你的代码时,会抛出SyntaxError,但是这些行并没有被实际执行。
类型错误
当您的代码试图执行无法在对象上执行的操作时,将会引发TypeError,例如试图将字符串添加到整数中,或者对长度未定义的对象调用len()。Python文档定义了何时抛出该异常:
当操作或函数应用于不适当类型的对象时引发。
下面是引发TypeError的几个示例:
上述所有引发TypeError的示例都会产生一个包含不同消息的错误消息行。每条消息都可以很好地告诉你哪里出错了。
前两个示例尝试将字符串和整数相加。但是,它们略有不同:
第一个试图将一个str加到一个int。第二个试图将一个int 加到一个 str。
错误信息行反映了这些差异。
最后一个示例尝试对int调用len()。错误信息行告诉您不能对int类型执行此操作。
值错误
当对象的值不正确时,将抛出ValueError。你可以把它想成一个IndexError,当索引值不在序列的范围内的时候会引发,但是ValueError用在更一般的情况下。Python文档定义了何时抛出该异常:
当操作或函数接收到类型正确但值不合适的参数时抛出,这种情况无法用更准确的异常(如IndexError)来描述。
下面是引发ValueError的两个示例:
在这些示例中,ValueError错误消息行会告诉您这些值到底出了什么问题:
在第一个示例中,你试图解压缩太多的值。错误消息行甚至告诉你,你期望解压缩3个值,但是只得到了2个值。在第二个例子中,问题是你得到了太多的值,但没有足够的变量来解压缩它们。
如何记录回溯?
获得异常及其生成的Python回溯意味着您需要决定如何处理它。通常,修复代码是第一步,但有时问题在于意外或不正确的输入。虽然在代码中提供这些情况很好,但有时通过记录回溯和执行其他操作来隐藏异常是有意义的。
下面是一个更现实的代码示例,需要让一些Python回溯保持静默。这个例子使用了请求库。您可以在Python的请求库(指南)中获得更多信息:
这段代码运行良好。当您运行这个脚本时,您向它提供一个URL作为命令行参数,它将调用这个URL,然后打印出HTTP状态代码和响应的内容。即使响应是HTTP错误状态,它也能工作:
但是,有时您的脚本提供的用于检索的URL不存在,或者主机服务器被关闭。在这些情况下,脚本现在将抛出一个未捕获的ConnectionError异常,并打印一个回溯:
这里的Python回溯可能会很长,还会抛出很多其他异常,最终导致请求库本身抛出ConnectionError。如果向上移动到最后一个异常回溯,可以看到问题都是从我们的代码urlcall.py中的第5行开始的。
如果在try and except块中封装非法行,捕捉适当的异常将允许您的脚本继续处理更多的输入:
上面的代码使用了带有try和except块的else子句。如果您不熟悉Python的这个特性,请查看Python异常:简介中的else子句。
现在,当您使用一个URL来运行这个脚本时,将会引发一个连接错误,并且系统将会打印一个状态代码-1和Connection Error的内容:
这个很好用。然而,在大多数实际的系统中,你不只是想沉默异常和产生回溯,而是想记录回溯。记录回溯可以帮助你更好地理解程序中哪里出错了。
注意:要了解更多关于Python日志系统的信息,请查看Python中的日志记录。
您可以通过导入日志包、获取日志记录器并调用日志记录器的来在脚本中记录回溯。try和except块的except部分中的exception()。您的最终脚本应该类似于以下代码:
现在,当您在一个有问题的URL上运行这个脚本时,它将打印预期的-1和连接错误,并记录回溯:
默认情况下,Python会向标准错误(stderr)发送日志消息。似乎我们根本没有抑制追溯输出。但是,如果在重定向stderr时再次调用它,可以看到日志系统正在工作,我们可以保存日志以备后用:
结论
Python回溯包含了大量的信息,可以帮助你发现Python代码中的错误。这些回溯看起来有点吓人,但是一旦你把它分解,看看它想给你看什么,它们就会非常有用。逐行浏览一些回溯会帮助你更好地理解它们包含的信息,并帮助你充分利用它们。
运行代码时获得Python回溯输出是改进代码的机会。这是Python试图帮助你的一种方式。
既然您已经知道了如何阅读Python回溯,那么您可以从学习更多关于诊断回溯输出所告诉您的问题的工具和技术中受益。Python内置的回溯模块可以用来处理和检查回溯。当您需要从回溯输出中获取更多信息时,回溯模块非常有用。了解更多关于调试Python代码的技术也会有所帮助。
最后,小编整合了一些python的学习资料,供大家下载学习:领取方式:关注+转发,然后私聊小编“资料”即可领取!英文原文:https://realpython.com/python-traceback/ 译者:野生大熊猫
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。