總結:Python中的異常處理

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

異常處理語句 try...excpet...finally

實例代碼

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

總結以下

  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,其語法規則以下。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便可。測試

經驗案例

傳遞異常 re-raise Exception

捕捉到了異常,可是又想從新拋出它(傳遞異常),使用不帶參數的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

當咱們要捕獲一個通用異常時,應該用Exception仍是BaseException?我建議你仍是看一下 官方文檔說明,這兩個異常到底有啥區別呢? 請看它們之間的繼承關係。

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

Exception的層級結構來看,BaseException是最基礎的異常類,Exception繼承了它。BaseException除了包含全部的Exception外還包含了SystemExitKeyboardInterruptGeneratorExit三個異常。

由此看來你的程序在捕獲全部異常時更應該使用Exception而不是BaseException,由於被排除的三個異常屬於更高級別的異常,合理的作法應該是交給Python的解釋器處理。

except Exception as e和 except Exception, e

代碼示例以下:

try:
    do_something()
except NameError as e:  # should
    pass
except KeyError, e:  # should not
    pass

在Python2的時代,你可使用以上兩種寫法中的任意一種。在Python3中你只能使用第一種寫法,第二種寫法已經再也不支持。第一個種寫法可讀性更好,並且爲了程序的兼容性和後期移植的成本,請你果斷拋棄第二種寫法。

raise "Exception string"

把字符串當成異常拋出看上去是一個很是簡潔的辦法,但實際上是一個很是很差的習慣。

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 之前是能夠接受的作法,可是沒有指定異常類型有可能會讓下游沒辦法正確捕獲並處理這個異常,從而致使你的程序難以維護。簡單說,這種寫法是是封建時代的陋習,應該扔了。

使用內置的語法範式代替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 
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')

最佳實踐

最佳實踐不限於編程語言,只是一些規則和填坑後的收穫。

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

關於做者:Python技術愛好者,目前從事測試開發相關工做,轉載請註明原文出處。

歡迎關注個人博客 https://betacat.online,你能夠到個人公衆號中去當吃瓜羣衆。

Betacat.online

相關文章
相關標籤/搜索