Python中的異常處理

hfgerr 7年前發布 | 12K 次閱讀 Python Python開發

異常處理在任何一門編程語言里都是值得關注的一個話題,良好的異常處理可以讓你的程序更加健壯,清晰的錯誤信息更能幫助你快速修復問題。在Python中,和不部分高級語言一樣,使用了try/except/finally語句塊來處理異常,如果你有其他編程語言的經驗,實踐起來并不難。

異常處理語句 try…excpet…finally

實例代碼

defdiv(a, b):
    try:
        print(a / b)
    exceptZeroDivisionError:
        print("Error: b should not be 0 !!")
    exceptExceptionas 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

總結如下

  1. except 語句不是必須的, finally 語句也不是必須的,但是二者必須要有一個,否則就沒有 try 的意義了。
  2. except 語句可以有多個,Python會按 except 語句的順序依次匹配你指定的異常,如果異常已經處理就不會再進入后面的 except 語句。
  3. except 語句可以以元組形式同時指定多個異常,參見實例代碼。
  4. except 語句后面如果不指定異常類型,則默認捕獲所有異常,你可以通過logging或者sys模塊獲取當前異常。
  5. 如果要捕獲異常后要重復拋出,請使用 raise ,后面不要帶任何參數或信息。
  6. 不建議捕獲并拋出同一個異常,請考慮重構你的代碼。
  7. 不建議在不清楚邏輯的情況下捕獲所有異常,有可能你隱藏了很嚴重的問題。
  8. 盡量使用內置的異常處理語句來 替換try/except語句,比如 with 語句, getattr() 方法。

拋出異常 raise

如果你需要自主拋出異常一個異常,可以使用 raise 關鍵字,等同于C#和Java中的 throw 語句,其語法規則如下。

raiseNameError("bad name!")

raise 關鍵字后面需要指定你拋出的異常類型,一般來說拋出的異常越詳細越好,Python在 exceptions 模塊內建了很多的異常類型,通過使用 dir() 函數來查看 exceptions 中的異常類型,如下:

importexceptions
 
# ['ArithmeticError', 'AssertionError'.....]
printdir(exceptions)

當然你也可以查閱Python的文檔庫進行更詳細的了解。

自定義異常類型

Python中也可以自定義自己的特殊類型的異常,只需要要從 Exception 類繼承(直接或間接)即可:

class SomeCustomException(Exception):
    pass

一般你在自定義異常類型時,需要考慮的問題應該是這個異常所應用的場景。如果內置異常已經包括了你需要的異常,建議考慮使用內置 的異常類型。比如你希望在函數參數錯誤時拋出一個異常,你可能并不需要定義一個 InvalidArgumentError ,使用內置的 ValueError 即可。

經驗案例

傳遞異常 re-raise Exception

捕捉到了異常,但是又想重新引發它(傳遞異常),使用不帶參數的 raise 語句即可:

deff1():
    print(1/0)
 
deff2():
    try:
        f1()
    exceptExceptionas e:
        raise  # don't raise e !!!
 
f2()

在Python2中,為了保持異常的完整信息,那么你捕獲后再次拋出時千萬不能在 raise 后面加上異常對象,否則你的 trace 信息就會從此處截斷 。以上是最簡單的重新拋出異常的做法。

還有一些技巧可以考慮,比如拋出異常前對異常的信息進行更新。

deff2():
    try:
        f1()
    exceptExceptionas e:
        e.args += ('more info',)
        raise

如果你有興趣了解更多,建議閱讀這篇博客。

Python3對重復傳遞異常有所改進,你可以自己嘗試一下,不過建議還是同上。

Exception 和 BaseException

當我們要捕獲一個通用異常時,應該用 Exception 還是 BaseException ?

BaseException
 +-- SystemExit
 +-- KeyboardInterrupt
 +-- GeneratorExit
 +-- Exception
      +-- StopIteration...
      +-- StandardError...
      +-- Warning...

從 Exception 的層級結構來看, BaseException 是最基礎的異常類, Exception 繼承了它。 BaseException 除了包含所有的 Exception 外還包含了 SystemExit , KeyboardInterrupt 和 GeneratorExit 三個異常。

有此看來你的程序在捕獲所有異常時更應該使用 Exception 而不是 BaseException ,因為另外三個異常屬于更高級別的異常,合理的做法應該是交給Python的解釋器處理。

except Exception as e和 except Exception, e

代碼示例如下:

try:
    do_something()
exceptNameErroras e:  # should
    pass
exceptKeyError, e:  # should not
    pass

在Python2的時代,你可以使用以上兩種寫法中的任意一種。在Python3中你只能使用第一種寫法,第二種寫法被廢棄掉了。第一個種寫法可讀性更好,而且為了程序的兼容性和后期移植的成本,請你也拋棄第二種寫法。

raise “Exception string”

把字符串當成異常拋出看上去是一個非常簡潔的辦法,但其實是一個非常不好的習慣。

if is_work_done():
    pass
else:
    raise "Work is not done!" # not cool

上面的語句如果拋出異常,那么會是這樣的:

Traceback (mostrecentcalllast):
  File "/demo/exception_hanlding.py", line 48, in 
    raise "Work is not done!"
TypeError: exceptionsmustbeold-styleclassesor derivedfromBaseException, not str

這在Python2.4以前是可以接受的做法,但是沒有指定異常類型有可能會讓下游沒辦法正確捕獲并處理這個異常,從而導致你的程序掛掉。簡單說,這種寫法是是封建時代的陋習,應該扔了。

使用內置的語法范式代替try/except

Python 本身提供了很多的語法范式簡化了異常的處理,比如 for 語句就處理的 StopIteration 異常,讓你很流暢地寫出一個循環。

with 語句在打開文件后會自動調用 finally 中的關閉文件操作。我們在寫Python代碼時應該盡量避免在遇到這種情況時還使用try/except/finally的思維來處理。

# should not
try:
    f = open(a_file)
    do_something(f)
finally:
    f.close()
 
# should
withopen(a_file) as f:
    do_something(f)

再比如,當我們需要訪問一個不確定的屬性時,有可能你會寫出這樣的代碼:

try:
    test = Test()
    name = test.name  # not sure if we can get its name
exceptAttributeError:
    name = 'default'

其實你可以使用更簡單的 getattr() 來達到你的目的。

name = getattr(test, 'name', 'default')

最佳實踐

最佳實踐不限于編程語言,只是一些規則和填坑后的收獲。

  1. 只處理你知道的異常,避免捕獲所有 異常然后吞掉它們。
  2. 拋出的異常應該說明原因,有時候你知道異常類型也猜不出所以然的。
  3. 避免在catch語句塊中干一些沒意義的事情。
  4. 不要使用異常來控制流程,那樣你的程序會無比難懂和難維護。
  5. 如果有需要,切記使用finally來釋放資源。
  6. 如果有需要,請不要忘記在處理異常后做清理工作或者回滾操作。

 

來自:http://python.jobbole.com/87087/

 

 本文由用戶 hfgerr 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!