Python異常及處理方法總結

  • 2020 年 1 月 13 日
  • 筆記

調試Python程式時,經常會報出一些異常,異常的原因一方面可能是寫程式時由於疏忽或者考慮不全造成了錯誤,這時就需要根據異常Traceback到出錯點,進行分析改正;另一方面,有些異常是不可避免的,但我們可以對異常進行捕獲處理,防止程式終止。

1 異常類型

1.1 Python內置異常

Python的異常處理能力是很強大的,它有很多內置異常,可向用戶準確回饋出錯資訊。在Python中,異常也是對象,可對它進行操作。BaseException是所有內置異常的基類,但用戶定義的類並不直接繼承BaseException,所有的異常類都是從Exception繼承,且都在exceptions模組中定義。Python自動將所有異常名稱放在內建命名空間中,所以程式不必導入exceptions模組即可使用異常。一旦引發而且沒有捕捉SystemExit異常,程式執行就會終止。如果互動式會話遇到一個未被捕捉的SystemExit異常,會話就會終止。

內置異常類的層次結構如下:

BaseException  # 所有異常的基類   +-- SystemExit  # 解釋器請求退出   +-- KeyboardInterrupt  # 用戶中斷執行(通常是輸入^C)   +-- GeneratorExit  # 生成器(generator)發生異常來通知退出   +-- Exception  # 常規異常的基類        +-- StopIteration  # 迭代器沒有更多的值        +-- StopAsyncIteration  # 必須通過非同步迭代器對象的__anext__()方法引發以停止迭代        +-- ArithmeticError  # 各種算術錯誤引發的內置異常的基類        |    +-- FloatingPointError  # 浮點計算錯誤        |    +-- OverflowError  # 數值運算結果太大無法表示        |    +-- ZeroDivisionError  # 除(或取模)零 (所有數據類型)        +-- AssertionError  # 當assert語句失敗時引發        +-- AttributeError  # 屬性引用或賦值失敗        +-- BufferError  # 無法執行與緩衝區相關的操作時引發        +-- EOFError  # 當input()函數在沒有讀取任何數據的情況下達到文件結束條件(EOF)時引發        +-- ImportError  # 導入模組/對象失敗        |    +-- ModuleNotFoundError  # 無法找到模組或在在sys.modules中找到None        +-- LookupError  # 映射或序列上使用的鍵或索引無效時引發的異常的基類        |    +-- IndexError  # 序列中沒有此索引(index)        |    +-- KeyError  # 映射中沒有這個鍵        +-- MemoryError  # 記憶體溢出錯誤(對於Python 解釋器不是致命的)        +-- NameError  # 未聲明/初始化對象 (沒有屬性)        |    +-- UnboundLocalError  # 訪問未初始化的本地變數        +-- OSError  # 作業系統錯誤,EnvironmentError,IOError,WindowsError,socket.error,select.error和mmap.error已合併到OSError中,構造函數可能返回子類        |    +-- BlockingIOError  # 操作將阻塞對象(e.g. socket)設置為非阻塞操作        |    +-- ChildProcessError  # 在子進程上的操作失敗        |    +-- ConnectionError  # 與連接相關的異常的基類        |    |    +-- BrokenPipeError  # 另一端關閉時嘗試寫入管道或試圖在已關閉寫入的套接字上寫入        |    |    +-- ConnectionAbortedError  # 連接嘗試被對等方中止        |    |    +-- ConnectionRefusedError  # 連接嘗試被對等方拒絕        |    |    +-- ConnectionResetError    # 連接由對等方重置        |    +-- FileExistsError  # 創建已存在的文件或目錄        |    +-- FileNotFoundError  # 請求不存在的文件或目錄        |    +-- InterruptedError  # 系統調用被輸入訊號中斷        |    +-- IsADirectoryError  # 在目錄上請求文件操作(例如 os.remove())        |    +-- NotADirectoryError  # 在不是目錄的事物上請求目錄操作(例如 os.listdir())        |    +-- PermissionError  # 嘗試在沒有足夠訪問許可權的情況下運行操作        |    +-- ProcessLookupError  # 給定進程不存在        |    +-- TimeoutError  # 系統函數在系統級別超時        +-- ReferenceError  # weakref.proxy()函數創建的弱引用試圖訪問已經垃圾回收了的對象        +-- RuntimeError  # 在檢測到不屬於任何其他類別的錯誤時觸發        |    +-- NotImplementedError  # 在用戶定義的基類中,抽象方法要求派生類重寫該方法或者正在開發的類指示仍然需要添加實際實現        |    +-- RecursionError  # 解釋器檢測到超出最大遞歸深度        +-- SyntaxError  # Python 語法錯誤        |    +-- IndentationError  # 縮進錯誤        |         +-- TabError  # Tab和空格混用        +-- SystemError  # 解釋器發現內部錯誤        +-- TypeError  # 操作或函數應用於不適當類型的對象        +-- ValueError  # 操作或函數接收到具有正確類型但值不合適的參數        |    +-- UnicodeError  # 發生與Unicode相關的編碼或解碼錯誤        |         +-- UnicodeDecodeError  # Unicode解碼錯誤        |         +-- UnicodeEncodeError  # Unicode編碼錯誤        |         +-- UnicodeTranslateError  # Unicode轉碼錯誤        +-- Warning  # 警告的基類             +-- DeprecationWarning  # 有關已棄用功能的警告的基類             +-- PendingDeprecationWarning  # 有關不推薦使用功能的警告的基類             +-- RuntimeWarning  # 有關可疑的運行時行為的警告的基類             +-- SyntaxWarning  # 關於可疑語法警告的基類             +-- UserWarning  # 用戶程式碼生成警告的基類             +-- FutureWarning  # 有關已棄用功能的警告的基類             +-- ImportWarning  # 關於模組導入時可能出錯的警告的基類             +-- UnicodeWarning  # 與Unicode相關的警告的基類             +-- BytesWarning  # 與bytes和bytearray相關的警告的基類             +-- ResourceWarning  # 與資源使用相關的警告的基類。被默認警告過濾器忽略。

詳細說明請參考:https://docs.python.org/3/library/exceptions.html#base-classes

1.2 requests模組的相關異常

在做爬蟲時,requests是一個十分好用的模組,所以我們在這裡專門探討一下requests模組相關的異常。

要調用requests模組的內置異常,只要「from requests.exceptions import xxx」就可以了,比如:

from requests.exceptions import ConnectionError, ReadTimeout

或者直接這樣也是可以的:

from requests import ConnectionError, ReadTimeout

requests模組內置異常類的層次結構如下:

IOError   +-- RequestException  # 處理不確定的異常請求        +-- HTTPError  # HTTP錯誤        +-- ConnectionError  # 連接錯誤        |    +-- ProxyError  # 代理錯誤        |    +-- SSLError  # SSL錯誤        |    +-- ConnectTimeout(+-- Timeout)  # (雙重繼承,下同)嘗試連接到遠程伺服器時請求超時,產生此錯誤的請求可以安全地重試。        +-- Timeout  # 請求超時        |    +-- ReadTimeout  # 伺服器未在指定的時間內發送任何數據        +-- URLRequired  # 發出請求需要有效的URL        +-- TooManyRedirects  # 重定向太多        +-- MissingSchema(+-- ValueError) # 缺少URL架構(例如http或https)        +-- InvalidSchema(+-- ValueError) # 無效的架構,有效架構請參見defaults.py        +-- InvalidURL(+-- ValueError)  # 無效的URL        |    +-- InvalidProxyURL  # 無效的代理URL        +-- InvalidHeader(+-- ValueError)  # 無效的Header        +-- ChunkedEncodingError  # 伺服器聲明了chunked編碼但發送了一個無效的chunk        +-- ContentDecodingError(+-- BaseHTTPError)  # 無法解碼響應內容        +-- StreamConsumedError(+-- TypeError)  # 此響應的內容已被使用        +-- RetryError  # 自定義重試邏輯失敗        +-- UnrewindableBodyError  # 嘗試倒回正文時,請求遇到錯誤        +-- FileModeWarning(+-- DeprecationWarning)  # 文件以文本模式打開,但Requests確定其二進位長度        +-- RequestsDependencyWarning  # 導入的依賴項與預期的版本範圍不匹配    Warning   +-- RequestsWarning  # 請求的基本警告

詳細說明及源碼請參考:http://www.python-requests.org/en/master/_modules/requests/exceptions/#RequestException

下面是一個簡單的小例子,python內置了一個ConnectionError異常,這裡可以不用再從requests模組import了:

import requests  from requests import ReadTimeout      def get_page(url):  	try:  		response = requests.get(url, timeout=1)  		if response.status_code == 200:  			return response.text  		else:  			print('Get Page Failed', response.status_code)  			return None  	except (ConnectionError, ReadTimeout):  		print('Crawling Failed', url)  		return None      def main():  	url = 'https://www.baidu.com'  	print(get_page(url))      if __name__ == '__main__':  	main()

1.3 用戶自定義異常

此外,你也可以通過創建一個新的異常類擁有自己的異常,異常應該是通過直接或間接的方式繼承自Exception類。下面創建了一個MyError類,基類為Exception,用於在異常觸發時輸出更多的資訊。

  在try語句塊中,拋出用戶自定義的異常後執行except部分,變數 e 是用於創建MyError類的實例。

class MyError(Exception):  	def __init__(self, msg):  		self.msg = msg    	def __str__(self):  		return self.msg      try:  	raise MyError('類型錯誤')  except MyError as e:  	print('My exception occurred', e.msg)

2. 異常捕獲

當發生異常時,我們就需要對異常進行捕獲,然後進行相應的處理。python的異常捕獲常用try…except…結構,把可能發生錯誤的語句放在try模組里,用except來處理異常,每一個try,都必須至少對應一個except。此外,與python異常相關的關鍵字主要有:

關鍵字

關鍵字說明

try/except

捕獲異常並處理

pass

忽略異常

as

定義異常實例(except MyError as e)

else

如果try中的語句沒有引發異常,則執行else中的語句

finally

無論是否出現異常,都執行的程式碼

raise

拋出/引發異常

異常捕獲有很多方式,下面分別進行討論。

2.1 捕獲所有異常

包括鍵盤中斷和程式退出請求(用sys.exit()就無法退出程式了,因為異常被捕獲了),因此慎用。

try:       <語句>    except:          print('異常說明')

2.2 捕獲指定異常

try:       <語句>    except <異常名>:          print('異常說明')

萬能異常:

try:       <語句>    except Exception:          print('異常說明')

一個例子:

try:      f = open("file-not-exists", "r")    except IOError as e:        print("open exception: %s: %s" %(e.errno, e.strerror))

2.3 捕獲多個異常

捕獲多個異常有兩種方式,第一種是一個except同時處理多個異常,不區分優先順序:

try:       <語句>    except (<異常名1>, <異常名2>, ...):          print('異常說明')

第二種是區分優先順序的:

try:       <語句>    except <異常名1>:          print('異常說明1')    except <異常名2>:          print('異常說明2')    except <異常名3>:          print('異常說明3')

該種異常處理語法的規則是:

  • 執行try下的語句,如果引發異常,則執行過程會跳到第一個except語句。
  • 如果第一個except中定義的異常與引發的異常匹配,則執行該except中的語句。
  • 如果引發的異常不匹配第一個except,則會搜索第二個except,允許編寫的except數量沒有限制。
  • 如果所有的except都不匹配,則異常會傳遞到下一個調用本程式碼的最高層try程式碼中。

2.4 異常中的else

如果判斷完沒有某些異常之後還想做其他事,就可以使用下面這樣的else語句。

try:       <語句>    except <異常名1>:          print('異常說明1')    except <異常名2>:          print('異常說明2')    else:          <語句>  # try語句中沒有異常則執行此段程式碼

2.5 異常中的finally

try…finally…語句無論是否發生異常都將會執行最後的程式碼。

try:       <語句>    finally:          <語句>

看一個示例:

str1 = 'hello world'  try:      int(str1)  except IndexError as e:      print(e)  except KeyError as e:      print(e)  except ValueError as e:      print(e)  else:      print('try內沒有異常')  finally:      print('無論異常與否,都會執行我')

2.6 raise主動觸發異常

可以使用raise語句自己觸發異常,raise語法格式如下:

raise [Exception [, args [, traceback]]]

語句中Exception是異常的類型(例如ValueError),參數是一個異常參數值。該參數是可選的,如果不提供,異常的參數是"None"。最後一個參數是跟蹤異常對象,也是可選的(在實踐中很少使用)。

看一個例子:

def not_zero(num):      try:          if num == 0:              raise ValueError('參數錯誤')          return num      except Exception as e:          print(e)      not_zero(0)

2.7 採用traceback模組查看異常

發生異常時,Python能「記住」引發的異常以及程式的當前狀態。Python還維護著traceback(跟蹤)對象,其中含有異常發生時與函數調用堆棧有關的資訊。記住,異常可能在一系列嵌套較深的函數調用中引發。程式調用每個函數時,Python會在「函數調用堆棧」的起始處插入函數名。一旦異常被引發,Python會搜索一個相應的異常處理程式。如果當前函數中沒有異常處理程式,當前函數會終止執行,Python會搜索當前函數的調用函數,並以此類推,直到發現匹配的異常處理程式,或者Python抵達主程式為止。這一查找合適的異常處理程式的過程就稱為「堆棧輾轉開解」(StackUnwinding)。解釋器一方面維護著與放置堆棧中的函數有關的資訊,另一方面也維護著與已從堆棧中「輾轉開解」的函數有關的資訊。

格式如下:

try:      block    except:        traceback.print_exc()

舉個栗子:

try:      1/0  except Exception as e:      print(e)

如果我們這樣寫的話,程式只會報「division by zero」錯誤,但是我們並不知道是在哪個文件哪個函數哪一行出的錯。

下面使用traceback模組,官方參考文檔:https://docs.python.org/2/library/traceback.html

import traceback    try:      1/0  except Exception as e:      traceback.print_exc()

這樣就會幫我們追溯到出錯點:

Traceback (most recent call last):    File "E:/PycharmProjects/ProxyPool-master/proxypool/test.py", line 4, in <module>      1/0  ZeroDivisionError: division by zero

另外,traceback.print_exc()跟traceback.format_exc()有什麼區別呢?

區別就是,format_exc()返回字元串,print_exc()則直接給列印出來。即traceback.print_exc()與print(traceback.format_exc())效果是一樣的。print_exc()還可以接受file參數直接寫入到一個文件。比如可以像下面這樣把相關資訊寫入到tb.txt文件去。

traceback.print_exc(file=open('tb.txt','w+'))

參考博文:

except as e中的『e』的作用總結

python使用traceback獲取詳細的異常資訊