一文教你讀懂 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 的腳本時,不只會打印錯誤,同時還會在日誌文件中記錄這些錯誤信息。過於日誌的其餘信息能夠看我以前的文章。

相關文章
相關標籤/搜索