一文掌握 Python 異常處理的全部知識點

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



什麼是異常?python



1.錯誤程序員

從軟件方面來講,錯誤是語法或是邏輯上的。錯誤是語法或是邏輯上的。編程

語法錯誤指示軟件的結構上有錯誤,致使不能被解釋器解釋或編譯器沒法編譯。這些些錯誤必須在程序執行前糾正。編程語言

當程序的語法正確後,剩下的就是邏輯錯誤了。邏輯錯誤多是因爲不完整或是不合法的輸入所致;ide

在其它狀況下,還多是邏輯沒法生成、計算、或是輸出結果須要的過程沒法執行。這些錯誤一般分別被稱爲域錯誤和範圍錯誤。spa

當python檢測到一個錯誤時,python解釋器就會指出當前流已經沒法繼續執行下去。這時候就出現了異常。orm

 

2.異常對象

對異常的最好描述是:它是由於程序出現了錯誤而在正常控制流之外採起的行爲。繼承

這個行爲又分爲兩個階段:首先是引發異常發生的錯誤,而後是檢測(和採起可能的措施)階段。ip

第一階段是在發生了一個異常條件(有時候也叫作例外的條件)後發生的。

只要檢測到錯誤而且意識到異常條件,解釋器就會發生一個異常。引起也能夠叫作觸發,拋出或者生成。解釋器經過它通知當前控制流有錯誤發生。

python也容許程序員本身引起異常。不管是python解釋器仍是程序員引起的,異常就是錯誤發生的信號。

當前流將被打斷,用來處理這個錯誤並採起相應的操做。這就是第二階段。

對於異常的處理髮生在第二階段,異常引起後,能夠調用不少不一樣的操做。

能夠是忽略錯誤(記錄錯誤但不採起任何措施,採起補救措施後終止程序。)或是減輕問題的影響後設法繼續執行程序。

全部的這些操做都表明一種繼續,或是控制的分支。關鍵是程序員在錯誤發生時能夠指示程序如何執行。

python用異常對象(exception object)來表示異常。遇到錯誤後,會引起異常。

若是異常對象並未被處理或捕捉,程序就會用所謂的回溯(traceback)終止執行



異常處理



捕捉異常可使用try/except語句。

try/except語句用來檢測try語句塊中的錯誤,從而讓except語句捕獲異常信息並處理。

若是你不想在異常發生時結束你的程序,只需在try裏捕獲它。

語法:

如下爲簡單的try....except...else的語法:

try:
<語句>        #運行別的代碼
except <名字>:
<語句>        #若是在try部份引起了'name'異常
except <名字>,<數據>:
<語句>        #若是引起了'name'異常,得到附加的數據
else:
<語句>        #若是沒有異常發生

Try的工做原理是,當開始一個try語句後,python就在當前程序的上下文中做標記,這樣當異常出現時就能夠回到這裏,try子句先執行,接下來會發生什麼依賴於執行時是否出現異常。

  1. 若是當try後的語句執行時發生異常,python就跳回到try並執行第一個匹配該異常的except子句,異常處理完畢,控制流就經過整個try語句(除非在處理異常時又引起新的異常)。

  2. 若是在try後的語句裏發生了異常,卻沒有匹配的except子句,異常將被遞交到上層的try,或者到程序的最上層(這樣將結束程序,並打印缺省的出錯信息)。

  3. 若是在try子句執行時沒有發生異常,python將執行else語句後的語句(若是有else的話),而後控制流經過整個try語句。

使用except而不帶任何異常類型

你能夠不帶任何異常類型使用except,以下實例:

try:
   正常的操做
   ......................
except:
   發生異常則執行此處代碼
   ......................
else:
   沒有異常則執行此處代碼

以上方式try-except語句捕獲全部發生的異常。但這不是一個很好的方式,咱們不能經過該程序識別出具體的異常信息。由於它捕獲全部的異常。

使用except而帶多種異常類型

你也可使用相同的except語句來處理多個異常信息,以下所示:

try:
   正常的操做
   ......................
except(Exception1[, Exception2[,...ExceptionN]]]):
  發生以上多個異常中的一個,執行這塊代碼
   ......................
else:
   若是沒有異常執行這塊代碼

try-finally 語句

try-finally 語句不管是否發生異常都將執行最後的代碼。

try:
<語句>
finally:
<語句>    #退出try時總會執行
raise

當在try塊中拋出一個異常,當即執行finally塊代碼。

finally塊中的全部語句執行後,異常被再次觸發,並執行except塊代碼。

參數的內容不一樣於異常。

下面來看一個實例:

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()方法。



經驗案例



傳遞異常 re-raise Exception
捕捉到了異常,可是又想從新引起它(傳遞異常),使用不帶參數的raise語句便可:

def f1():
    print(1/0)
def f2():
    try:
        f1()
    except Exception as e:
        raise  # don't raise e !!!
f2()

在Python2中,爲了保持異常的完整信息,那麼你捕獲後再次拋出時千萬不能在raise後面加上異常對象,不然你的trace信息就會今後處截斷。以上是最簡單的從新拋出異常的作法。

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

def f2():
    try:
        f1()
    except Exception as 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()
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()來達到你的目的。



最佳實踐



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

1.只處理你知道的異常捕獲所異常而後吞掉它們。

2.拋出的異常應該說明緣由,有時候你知道異常類型也猜不出因此然的。

3.避免在catch語句塊中幹一些沒意義的事情。

4.不要使用異常來控制流程,那樣你的程序會無比難懂和難維護。

5.若是有須要,切記使用finally來釋放資源。

6若是有須要,請不要忘記在處理異常後作清理工做或者回滾操做。



異常速查表



0.jpg

相關文章
相關標籤/搜索