異常處理在任何一門編程語言裏都是值得關注的一個話題,良好的異常處理可讓你的程序更加健壯,清晰的錯誤信息更能幫助你快速修復問題。在Python中,和不部分高級語言同樣,使用了try/except/finally語句塊來處理異常,若是你有其餘編程語言的經驗,實踐起來並不難。html
def div(a, b): try: print(a / b) except ZeroDivisionError: print("Error: b should not be 0 !!") except Exception as e: print("Unexpected Error: {}".format(e)) else: print('Run into else only when everything goes well') finally: print('Always run into finally block.') # tests div(2, 0) div(2, 'bad type') div(1, 2) # Mutiple exception in one line try: print(a / b) except (ZeroDivisionError, TypeError) as e: print(e) # Except block is optional when there is finally try: open(database) finally: close(database) # catch all errors and log it try: do_work() except: # get detail from logging module logging.exception('Exception caught!') # get detail from sys.exc_info() method error_type, error_value, trace_back = sys.exc_info() print(error_value) raise
except
語句不是必須的,finally
語句也不是必須的,可是兩者必需要有一個,不然就沒有try
的意義了。except
語句能夠有多個,Python會按except
語句的順序依次匹配你指定的異常,若是異常已經處理就不會再進入後面的except
語句。except
語句能夠以元組形式同時指定多個異常,參見實例代碼。except
語句後面若是不指定異常類型,則默認捕獲全部異常,你能夠經過logging或者sys模塊獲取當前異常。raise
,後面不要帶任何參數或信息。try/except
語句,好比with
語句,getattr()
方法。若是你須要自主拋出異常一個異常,可使用raise
關鍵字,等同於C#和Java中的throw
,其語法規則以下。python
raise NameError("bad name!")
raise
關鍵字後面能夠指定你要拋出的異常實例,通常來講拋出的異常越詳細越好,Python在exceptions
模塊內建了不少的異常類型,經過使用dir()
函數來查看exceptions
中的異常類型,以下:編程
import exceptions print dir(exceptions) # ['ArithmeticError', 'AssertionError'...]
固然你也能夠查閱Python的文檔庫進行更詳細的瞭解。編程語言
Python中自定義本身的異常類型很是簡單,只須要要從Exception
類繼承便可(直接或間接):函數
class SomeCustomException(Exception): pass class AnotherException(SomeCustomException): pass
通常你在自定義異常類型時,須要考慮的問題應該是這個異常所應用的場景。若是內置異常已經包括了你須要的異常,建議考慮使用內置的異常類型。好比你但願在函數參數錯誤時拋出一個異常,你可能並不須要定義一個InvalidArgumentError
,使用內置的ValueError
便可。測試
捕捉到了異常,可是又想從新拋出它(傳遞異常),使用不帶參數的raise
語句便可:spa
def f1(): print(1/0) def f2(): try: f1() except Exception as e: raise # don't raise e !!! f2()
在Python2中,爲了保持異常的完整信息,那麼你捕獲後再次拋出時千萬不能在raise
後面加上異常對象,不然你的trace
信息就會今後處截斷。以上是最簡單的從新拋出異常的作法,也是推薦的作法。code
還有一些技巧能夠考慮,好比拋出異常前你但願對異常的信息進行更新。orm
def f2(): try: f1() except Exception as e: e.args += ('more info',) raise
若是你有興趣瞭解更多,建議閱讀這篇博客。htm
Python3對重複傳遞異常有所改進,你能夠本身嘗試一下,不過建議仍是遵循以上規則。
當咱們要捕獲一個通用異常時,應該用Exception
仍是BaseException
?我建議你仍是看一下 官方文檔說明,這兩個異常到底有啥區別呢? 請看它們之間的繼承關係。
BaseException +-- SystemExit +-- KeyboardInterrupt +-- GeneratorExit +-- Exception +-- StopIteration... +-- StandardError... +-- Warning...
從Exception
的層級結構來看,BaseException
是最基礎的異常類,Exception
繼承了它。BaseException
除了包含全部的Exception
外還包含了SystemExit
,KeyboardInterrupt
和GeneratorExit
三個異常。
由此看來你的程序在捕獲全部異常時更應該使用Exception
而不是BaseException
,由於被排除的三個異常屬於更高級別的異常,合理的作法應該是交給Python的解釋器處理。
代碼示例以下:
try: do_something() except NameError as e: # should pass except KeyError, e: # should not pass
在Python2的時代,你可使用以上兩種寫法中的任意一種。在Python3中你只能使用第一種寫法,第二種寫法已經再也不支持。第一個種寫法可讀性更好,並且爲了程序的兼容性和後期移植的成本,請你果斷拋棄第二種寫法。
把字符串當成異常拋出看上去是一個很是簡潔的辦法,但實際上是一個很是很差的習慣。
if is_work_done(): pass else: raise "Work is not done!" # not cool
上面的語句若是拋出異常,那麼會是這樣的:
Traceback (most recent call last): File "/demo/exception_hanlding.py", line 48, in <module> raise "Work is not done!" TypeError: exceptions must be old-style classes or derived from BaseException, not str
這在 Python2.4 之前是能夠接受的作法,可是沒有指定異常類型有可能會讓下游沒辦法正確捕獲並處理這個異常,從而致使你的程序難以維護。簡單說,這種寫法是是封建時代的陋習,應該扔了。
Python 自己提供了不少的語法範式簡化了異常的處理,好比for
語句就處理了的StopIteration
異常,讓你很流暢地寫出一個循環。
with
語句在打開文件後會自動調用finally
並關閉文件。咱們在寫 Python 代碼時應該儘可能避免在遇到這種狀況時還使用try/except/finally的思惟來處理。
# should not try: f = open(a_file) do_something(f) finally: f.close() # should with open(a_file) as f: do_something(f)
再好比,當咱們須要訪問一個不肯定的屬性時,有可能你會寫出這樣的代碼:
try: test = Test() name = test.name # not sure if we can get its name except AttributeError: name = 'default'
其實你可使用更簡單的getattr()
來達到你的目的。
name = getattr(test, 'name', 'default')
最佳實踐不限於編程語言,只是一些規則和填坑後的收穫。
catch
語句塊中幹一些沒意義的事情,捕獲異常也是須要成本的。finally
來釋放資源。關於做者:Python技術愛好者,目前從事測試開發相關工做,轉載請註明原文出處。
歡迎關注個人博客 https://betacat.online,你能夠到個人公衆號中去當吃瓜羣衆。