當前位置:
首頁 > 知識 > 一文讀懂Python中的異常處理

一文讀懂Python中的異常處理

源 

| 哎媽呀Bug 


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


異常處理語句 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語句,其語法規則如下。

raise

 

NameError(

"bad name!"

)


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

import

 

exceptions
# [

"ArithmeticError"

,

 

"AssertionError"

.....]

print

 

dir(exceptions)


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


https://docs.python.org/2.7/library/exceptions.html#bltin-exceptions


自定義異常類型


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

class

 

SomeCustomException

(Exception)

:


   

pass


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


經驗案例


傳遞異常 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


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


http://www.ianbicking.org/blog/2007/09/re-raising-exceptions.html


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()來達到你的目的。

name

 

= getattr(test,

 

"name"

,

 

"default"

)


最佳實踐


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




  1. 只處理你知道的異常,避免捕獲所有 異常然後吞掉它們。



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



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



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



  5. 如果有需要,切記使用finally來釋放資源。



  6. 如果有需要,請不要忘記在處理異常後做清理工作或者回滾操作。


近期熱文




  • 乾貨 | NLP中的self-attention【自-注意力】機制



  • 推薦 | 機器學習中的這12條經驗,希望對你有所幫助



  • 圖解機器學習的常見演算法




  • 利用Python實現卷積神經網路的可視化




  • 乾貨|淺談強化學習的方法及學習路線


廣告、商業合作


請添加微信:guodongwe1991


(備註:商務合作)

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!

TAG: |