一文教你读懂 Python 中的异常信息

做者|Chad Hansen
来源|Python学习开发

在写 Python 代码的时候,当代码中出现错误,会在输出的时候打印 Traceback 错误信息,不少初学者看到那一堆错误信息,每每都会处于懵逼状态,脑中总会冒出一句,这都是些啥玩意。若是你是第一次看到它,也许你不知道它在告诉你什么。虽然 Python 的 Traceback 提示信息看着挺复杂,可是里面丰富的信息,能够帮助你诊断和修复代码中引起异常的缘由,以及定位到具体哪一个文件的哪行代码出现的错误,因此说学会看懂 Traceback 信息是很是重要的,另外在面试的时候也常常会问到 Python 中的异常类型及其含义,那么,接下来就让咱们对其进行详细理解。node

什么是 Traceback

Traceback 是 Python 错误信息的报告。在其余编程语言中有着不一样的叫法包括 stack trace, stack traceback, backtrac 等名称, 在 Python 中,咱们使用的术语是 Traceback。后面我提到的错误信息等词都表示Traceback。
当你的程序致使异常时,Python 将打印 Traceback 以帮助你知道哪里出错了。下面是一个例子来讲明这种状况python

# example.py 
def  greet(someone ):
    print('Hello, ' + someon )

greet('Chad')

这里首先定义了函数 greet,而后传入参数 someone,而后函数内,一个 print 语句其中 someon 是一个没有定义的变量,
而后经过 greet ('Chad'),调用刚才定义的 greet 函数,运行以后会出现以下错误信息。
(Python 中的错误信息开头就是 Traceback。)面试

Traceback  (most  recent  call  last ):
  File  "/Users/chenxiangan/pythonproject/demo/exmpale.py", line  5, in  <module>
    greet ('Chad')
  File  "/Users/chenxiangan/pythonproject/demo/exmpale.py", line  3, in  greet 
    print ('Hello, ' + someon )
NameError: name  'someon' is  not  defined

此错误输出包含诊断问题所需的全部信息。错误输出的最后一行通常会告诉你引起了什么类型的异常,以及关于该异常的一些相关信息。错误信息的前几行指出了引起异常的代码文件以及行数。
在上面的错误信息中,异常类型是 NameError,意思是名称使用了一个没定义的名称(变量、函数、类)的引用。在本例中,引用的名称是 someon。
通常状况下看错误信息的最后一行就能定位到错误的缘由。而后在代码中搜索错误提示中的名称"someon",而后发现这是一个拼写错误,而后咱们改为 someone 便可。
然而,有些代码的错误信息要比这个复杂的多。编程

如何阅读 Python 的 Traceback 信息?

当你想肯定代码为何引起异常的时侯,能够根据 Python 的 Traceback 获取许多有用的信息。下面,将列举一些常见的 Traceback,以便理解 Tracebac 中包含的不一样信息。json

Python Traceback 信息一览

每一个 Python 的 Traceback 信息都有几个重要的部分。下图显示了各个组成部分:windows

  • 蓝框:Traceback 的最后一行为错误消息行。其中包含引起的异常名称。
  • 绿框:异常名称后面是错误消息。此消息一般包含有用的信息,用于了解引起异常的缘由。
  • 黄色方框:阅读顺序由下而上,最下面的信息,是抛出错误的最外层的位置,越往上代码调用深度越深。
    而后每一个出错的文件会有两条错误信息,第一行是 File 后面紧跟着文件的路径,而后是行数,最后是模块或者方法名。
    在 Pycharm 中点击文件的连接便可定位到错误的位置。
  • 红色下划线:第二行就是实际执行的代码语句了。

一个具体的🌰

经过一些特定的 Traceback 信息,能够帮助咱们更好地理解并查看 Traceback 将提供什么信息。
经过下面的示例代码来讲明 Python 中 Traceback 所提供的信息api

def  who_to_greet(person ):
    return  person  if  person  else  input ('Greet  who? ')

def  greet(someone, greeting='Hello'):
    print(greeting  + ', ' + who_to_greet (someone ))

def  greet_many(people):
    for  person  in  people:
        try:
            greet(person )
        except  Exception:
            print ('hi, ' + person )

定义一个 who_to_greet 函数,而后接受一个值 person,并根据 if 判断返回相应结果。
而后,greet 函数接受一个 someone 和一个可选的 greeting,以后调用 print 函数,在 print 中调用 who_to_greet 函数并传入参数 someone。
最后,greet_many(),将迭代 people 列表并调用 greet 函数。若是经过调用 greet()引起异常,则会打印一个简单的问候语。
只要提供了正确的输入,此代码就没有任何可能致使异常被引起的错误。
若是你在 greetings.py 中调用 greet 函数,并传入值(例如 greet ('chad',greting ='Yo')),那么你将得到如下 Traceback 信息session

Traceback  (most  recent  call  last ):
  File  "/Users/chenxiangan/pythonproject/demo/greetings.py", line  17, in  <module>
    greet ('chad',greting  ='Yo')
TypeError: greet () got  an  unexpected  keyword  argument  'greting'

以前咱们说过阅读 Python 的 Traceback 信息,是由下而上进行阅读的,这里咱们再一块儿看一看。
首先,咱们须要看的是错误信息的最后一行,经过最后一行能够知道错误的类型以及一些错误缘由。
意思是说:调用 greet()的时候使用了一个未知的参数,这个未知参数就是 greting。
好的,而后咱们须要继续向上看,能够看到致使异常的行。在这个例子中咱们看到的是调用 greet 方法的具体代码。
它的上一行提供了代码所在文件的路径,以及代码文件的行号以及它所在的模块。(Pycharm 中经过点击文件连接能够定位到具体位置)
在这个例子中,由于咱们的代码没有使用任何其余 Python 模块,因此咱们在这里看到<module>,它表示所处位置是在执行的文件。
使用不一样的文件和不一样的调用方式调用 greet 方法,获得的 Traceback 信息也是不一样的,下面就经过文件导入的形式来执行 greet 方法。看看结果有什么区别吧app

# example.py 
from  greetings  import  greet 
greet (1)

运行以后的结果编程语言

Traceback  (most  recent  call  last ):
  File  "/Users/chenxiangan/pythonproject/demo/exmpale.py", line  3, in  <module>
    greet (1)
  File  "/Users/chenxiangan/pythonproject/demo/greetings.py", line  6, in  greet 
    print (greeting  + ', ' + who_to_greet (someone ))
TypeError: can  only  concatenate  str  (not  "int") to  str

在本例中引起的异常一样是一个类型错误,但这一次消息的帮助要小一些。它只是告诉你,在代码的某个地方,字符串只能和字符串拼接,不能是 int。
向上移动,能够看到执行的代码行。而后是文件和行号的代码。不过,这一次咱们获得的不是,而是正在执行的函数的名称 greet()。
而后继续往上看,一行执行的代码,咱们看到问题代码是 greet()函数调用时传入了一个整数。
有时在引起异常以后,另外一部分代码会捕获该异常并致使异常。在这种状况下,Python 将按接收顺序输出全部异常信息,最外层的异常信息处于 Traceback 内容的最下面位置。
可能看起来有点懵,下面使用一个具体例子进行说明。
在 greetings.py 文件中调用 greet_many 方式具体调用代码以下:

greet_many (['Chad', 'Dan', 1])

运行以后输出的错误信息以下

Hello, Chad 
Hello, Dan 
Traceback  (most  recent  call  last ):
  File  "/Users/chenxiangan/pythonproject/demo/greetings.py", line  12, in  greet_many 
    greet (person )
  File  "/Users/chenxiangan/pythonproject/demo/greetings.py", line  6, in  greet 
    print (greeting  + ', ' + who_to_greet (someone ))
TypeError: can  only  concatenate  str  (not  "int") to  str 

During  handling  of  the  above  exception, another  exception  occurred:

Traceback  (most  recent  call  last ):
  File  "/Users/chenxiangan/pythonproject/demo/greetings.py", line  17, in  <module>
    greet_many (['Chad', 'Dan', 1])
  File  "/Users/chenxiangan/pythonproject/demo/greetings.py", line  14, in  greet_many 
    print ('hi, ' + person )
TypeError: can  only  concatenate  str  (not  "int") to  str

emmmmm,此次好像不太同样,比以前的内容多了很多,并且有两个 Traceback 块信息,这是什么意思呢?
注意这句话

During  handling  of  the  above  exception, another  exception  occurred:

它的意思是:在处理上述异常期间,发生了另外一个异常。简单理解就是在 except 中的代码出现了异常。因此致使了这种现象。
这个例子就是在第三次循环的时候 person=1 而后字符串 hi 和1 不能进行拼接操做,而后再次引起了异常。
查看全部的错误信息输出能够帮助您了解异常的真正缘由。
有时,当您看到最后一个异常被引起,并由此产生错误信息时,
你可能仍然看不出哪里出错了。好比这例子,直接经过最后的异常看不到问题具体出在哪,这个时候就要考虑继续往上看了。

Python 中有哪些常见的异常类型

在编程时,知道如何在程序引起异常时读取 Python 异常信息很是有用,若是再了解一些常见的异常类型那就更好了。
有时候在面试的时候也会遇到提问 Python 中常见的异常类型,以及其含义,因此这里也建议你们都了解如下。
下面就列举一些出现频次高并且很是重要的异常类型,但愿你们可以有必定的印象。

AttributeError

当你访问一个对象的属性,可是这个属性并无在这个对象定义的时候,就会引起 AttributeError。
下面是一个引起 AttributeError 异常的示例:

a  = 1
a.b

运行以后引起异常

Traceback  (most  recent  call  last ):
  File  "/Users/chenxiangan/pythonproject/demo/exmpale.py", line  2, in  <module>
    a.b 
AttributeError: 'int' object  has  no  attribute  'b'

AttributeError 的错误消息行告诉咱们特定对象类型(在本例中为 int)没有访问的属性,
在这个例子中属性为 b。点击文件连接能够快速定位到具体的错误代码的位置。

大多数状况下,引起这个异常代表你正在处理的对象可能不是你指望的类型。

a_list  = (1, 2)
a_list.append (3)

运行以后抛出异常信息

Traceback  (most  recent  call  last ):
  File  "/Users/chenxiangan/pythonproject/demo/exmpale.py", line  2, in  <module>
    a_list.append (3)
AttributeError: 'tuple' object  has  no  attribute  'append'

这里尝试给 a_list 对象进行 append 操做可是引起了异常,
这里的错误信息说,tuple 对象没有 append 属性。
缘由就是觉得 a_list 是列表可是实际上它是元组,
元组是不可变类型不支持添加元素操做因此出错了。这里也告诉你们,之后定义变量名的时候也要主要规范问题,不然就容易出现这种,指望类型错误的状况。
还有一种状况就是当对 None 进行属性操做的时候,很容易引起上面的异常

a_list  = None 
a_list.append (3)

运行抛出异常

Traceback  (most  recent  call  last ):
  File  "/Users/chenxiangan/pythonproject/demo/exmpale.py", line  2, in  <module>
    a_list.append (3)
AttributeError: 'NoneType' object  has  no  attribute  'append'

是否是很眼熟啊,遇到这种状况不要慌,分析看看你的哪一个对象是 None 就行了。

ImportError

在使用 import 导入模块时,若是要导入的模块找不到,或者从模块中导入模块中不存在的内容。这时就会触发 ImportError 类型的错误或者它的子类 ModuleNotFoundError。

import  aaa

运行后输出

Traceback  (most  recent  call  last ):
  File  "/Users/chenxiangan/pythonproject/demo/exmpale.py", line  1, in  <module>
    import  aaa 
ModuleNotFoundError: No  module  named  'aaa'

在这个例子中能够看到,当咱们使用 import 导入一个不存在的模块时,就会出现 ModuleNotFoundError 的错误,Traceback 最下面一句信息给出了缘由,
没有名为 aaa 的模块.
而后咱们再运行一个例子

from  collections  import  asdf

运行以后的内容

Traceback  (most  recent  call  last ):
  File  "/Users/chenxiangan/pythonproject/demo/exmpale.py", line  1, in  <module>
    from  collections  import  asdf 
ImportError: cannot  import  name  'asdf' from  'collections'

根据前面的经验咱们能够得知缘由,不能从 collections 模块中导入名为 asdf 的模块。
有时候为了程序能兼容在各个系统的时候,若是一个包找不到,找另外一个的时候,好比在 windows 中不能使用 ujson ,uvloop这两个包,可是在 unix 系统上是能够运行的,这个时候咱们就可使用下面的方法。

try:
    import  ujson  as  json 
except  ImportError  as  e:
    import  json

首先导入 ujson 而后使用 as 给他重命名为 json,若是出现错误就会进入 except 模块
而后导入标准库的 json 包,由于这边的库名已经叫 json 了因此不用再重命名了。记住这个技巧很是的有用哦。

IndexError

当你尝试从序列(如列表或元组)中检索索引,可是序列中找不到该索引。此时就会引起 IndexError。
例如

a_list  = ['a', 'b']
a_list[3]

运行以后的结果

Traceback  (most  recent  call  last ):
  File  "/Users/chenxiangan/pythonproject/demo/exmpale.py", line  2, in  <module>
    a_list[3]
IndexError: list  index  out  of  range

经过 IndexError 的错误消息的最后一不能获得一个准确的信息,只知道一个超出范围的序列引用以及序列的类型,在本例中是一个列表。咱们须要往上阅读错误信息,才能肯定错误的具体位置。这里咱们得知错误代码是 a_list[3]缘由是索引3 超出了列表的范围,由于最大就是1(索引下标从0 开始的)。

KeyError

与 IndexError 相似,当你访问映射(一般是 dict )中不包含的键时,就会引起 KeyError。

a_dict={}
a_dict['b']

运行以后

Traceback  (most  recent  call  last ):
  File  "/Users/chenxiangan/pythonproject/demo/exmpale.py", line  2, in  <module>
    a_dict['b']
KeyError: 'b'

KeyError 的错误消息行给出找不到关键字 b。并无太多的内容,可是,结合上面的错误信息,就能够解决这个问题。

NameError

当你引用了变量、模块、类、函数或代码中没有定义的其余名称时,将引起 NameError。

def  greet (person ):
    print (f'Hello, {persn}')
greet ('World')

运行以后

Traceback  (most  recent  call  last ):
  File  "/Users/chenxiangan/pythonproject/demo/exmpale.py", line  3, in  <module>
    greet ('World')
  File  "/Users/chenxiangan/pythonproject/demo/exmpale.py", line  2, in  greet 
    print (f'Hello, {persn}')
NameError: name  'persn' is  not  defined

NameError traceback 的错误消息行给出了缺失的名称 persn。
这个例子中,在 print 使用了没有定义过的变量 persn 因此出现了错误。
通常在拼写变量名出现问题时会引起这种错误。

SyntaxError

当代码中有不正确的 Python 语法时,就会引起 SyntaxError。
下面的问题是函数定义行末尾缺乏一个冒号。

def  greet (person )

运行以后

File  "/Users/chenxiangan/pythonproject/demo/exmpale.py", line  1
    def  greet (person )
                    ^
SyntaxError: invalid  syntax

SyntaxError 的错误消息行只告诉你代码的语法有问题。查看上面的行才能获得问题所在的行,一般会用一个^(插入符号)指向问题点。
此外,细心的朋友会注意到,在 SyntaxError 异常内容的第一行没有了以前的(most recent call last )。
这是由于 SyntaxError 是在 Python 尝试解析代码时引起的,实际上代码并无执行。

TypeError

当你的代码试图对一个没法执行此操做的对象执行某些操做时,例如将字符串添加到整数中,以及一开始的例子使用 append 方法给元组添加元素,这些都会引起 TypeError。
如下是引起 TypeError 的几个示例:

>>> 1 + '1'
Traceback  (most  recent  call  last ):
  File  "<stdin>", line  1, in  <module>
TypeError: unsupported  operand  type (s ) for  +: 'int' and  'str'
>>> '1' + 1
Traceback  (most  recent  call  last ):
  File  "<stdin>", line  1, in  <module>
TypeError: must  be  str, not  int 
>>> len (1)
Traceback  (most  recent  call  last ):
  File  "<stdin>", line  1, in  <module>
TypeError: object  of  type  'int' has  no  len ()

以上全部引起类型错误的示例都会产生包含不一样消息的错误消息行。它们每个都能很好地告诉你哪里出了问题。
前两个示例尝试将字符串和整数相加。然而,它们有细微的不一样

  • 第一个是尝试在 int 中拼接一个 str。
  • 第二个是尝试在 str 中拼接一个 int。

错误消息行反映了这些差别。
最后一个示例尝试在 int 上调用 len ()。
错误消息行告诉咱们不能使用 int 执行此操做。

ValueError

当对象的值不正确时就会引起 ValueError。这个和咱们前面说的由于索引的值不在序列的范围内,而致使 IndexError 异常相似。
下面看两个例子

>>> a, b, c  = [1, 2]
Traceback  (most  recent  call  last ):
  File  "<stdin>", line  1, in  <module>
ValueError: not  enough  values  to  unpack  (expected  3, got  2)
>>> a, b  = [1, 2, 3]
Traceback  (most  recent  call  last ):
  File  "<stdin>", line  1, in  <module>
ValueError: too  many  values  to  unpack  (expected  2)

这些示例中的 ValueError 错误消息行能够准确地告诉咱们值的一些问题:
在第一个示例中,错误信息行是没有足够多的值去 unpack (解包)。括号理面详细的写了你但愿解包3个值但实际上只给了2 个。
第二个示例中,错误信息行是解包太多的值。先解包3 个值可是只给了2 个变量,因此括号里提示 expected 2 就是说指望的实际是解包2 个值。
上面这些错误类型,基本上都是基础遇到的,但愿你们能熟悉记忆。

如何记录这些错误信息呢?

前面咱们说了不少异常的相关知识,可是咱们应该如何利用好呢,这里咱们就重点说一下,如何经过记录异常信息,方便后期程序的调试。
下面让咱们看一个关于使用 requests 模块的例子。
首先须要导入 requests 包,使用 pip 便可。

import  requests 
url  = "http://wwww.baidu.com"
response  = requests.get (url )

print (response.status_code, response.text )

这是一个访问百度的例子,运行以后,咱们成功获取了他的状态码和网页源码。
接下来咱们对 url 进行修改而后再运行。

import  requests 
url  = "http://urlis 233.com"
response  = requests.get (url )

print (response.status_code, response.text )

运行以后咱们发现程序出现了错误,下面分析下这些错误信息

省略前面部分
During  handling  of  the  above  exception, another  exception  occurred:

Traceback  (most  recent  call  last ):
  File  "/Users/chenxiangan/pythonproject/demo/exmpale.py", line  3, in  <module>
    response  = requests.get (url )
  File  "/Users/chenxiangan/pythonproject/demo/venv/lib/python 3.7/site-packages/requests/api.py", line  75, in  get 
    return  request ('get', url, params=params, **kwargs )
  File  "/Users/chenxiangan/pythonproject/demo/venv/lib/python 3.7/site-packages/requests/api.py", line  60, in  request 
    return  session.request (method=method, url=url, **kwargs )
  File  "/Users/chenxiangan/pythonproject/demo/venv/lib/python 3.7/site-packages/requests/sessions.py", line  533, in  request 
    resp  = self.send (prep, **send_kwargs )
  File  "/Users/chenxiangan/pythonproject/demo/venv/lib/python 3.7/site-packages/requests/sessions.py", line  646, in  send 
    r  = adapter.send (request, **kwargs )
  File  "/Users/chenxiangan/pythonproject/demo/venv/lib/python 3.7/site-packages/requests/adapters.py", line  516, in  send 
    raise  ConnectionError (e, request=request )
requests.exceptions.ConnectionError: HTTPConnectionPool (host='urlis 233.com', port=80): Max  retries  exceeded  with  url: / (Caused  by  NewConnectionError ('<urllib 3.connection.HTTPConnection  object  at  0x 10faeba 90>: Failed  to  establish  a  new  connection: [Errno  8] nodename  nor  servname  provided, or  not  known'))

这个错误信息很长,它引起了许多其余的异常,最终的异常类型是 requests.exceptions.ConnectionError。
往前面的错误信息找能够发现问题代码,

File  "/Users/chenxiangan/pythonproject/demo/exmpale.py", line  3, in  <module>
    response  = requests.get (url )

进而定位到错误,这个错误缘由主要是不存在地址"http://urlis http://233.com",因此访问失败。

错误咱们清楚了,可是一大堆的错误信息搭载控制台上,这样看很不美观,并且由于异常的缘由咱们的程序中断了。这个时候咱们就可使用 Python 中的异常处理模块 try/except 将代码改为下面这样

import  requests 
url  = "http://urlis 233.com"
try:
   response  = requests.get (url )
except  requests.exceptions.ConnectionError:
    print ("-1","连接有问题,访问失败")
else:
    print (response.status_code, response.text )

再次运行能够获得下面的结果

-1 连接有问题,访问失败

ok,咱们的程序能够正常运行了,输出的信息也美观了。
可是,在大多数实际系统中,咱们不但愿只是打印捕获的错误信息到控制台上,而是但愿记录这些信息,方便后面的错误排查,因此最好的方案就是经过日志的方式记录这些程序中的异常。

你能够经过导入 logging 模块,记录这些错误,最终代码以下

import  logging 
import  requests 

logger  = logging.getLogger (__name__)
url  = "http://urlis 233.com"

try:
    response  = requests.get (url )
except  requests.exceptions.ConnectionError  as  e:
    logger.exception ()
    print (-1, '连接有问题,访问失败')
else:
    print (response.status_code, response.content )

如今,当你再运行有问题的 URL 的脚本时,不只会打印错误,同时还会在日志文件中记录这些错误信息。过于日志的其余信息能够看我以前的文章。

相关文章
相关标签/搜索