Python異常編程的小技巧集錦
編程中經(jīng)常會(huì)需要使用到異常處理的情況,在閱讀了一些資料后,整理了關(guān)于異常處理的一些小技巧記錄如下。
如何自定義異常
定義異常類
在實(shí)際編程中,有時(shí)會(huì)發(fā)現(xiàn)Python提供的內(nèi)建異常的不夠用,我們需要在特殊業(yè)務(wù)場景下的異常。這時(shí)就需要我們來定義自己的異常。按照Python約定俗成的習(xí)慣,用戶定義的異常一般都是繼承于Exception類,由它開始拓展。后面我們可以看到這樣做在捕獲異常的時(shí)候會(huì)帶來很大的便利。
- >>> class MyError(Exception):
- pass
- >>> raise MyError(u"something error")
- Traceback (most recent call last):
- File "<stdin>", line 1, in <module>
- __main__.MyError: something error
API異常相關(guān)的技巧
API的異常分為定義異常與調(diào)用API時(shí)如何捕獲異常兩個(gè)部分,這二者相輔相成。
定義API異常的技巧
在自己編寫API的時(shí)候,應(yīng)該定義Root Exception——API中的根異常,其它異常都繼承于它。這樣的做法有兩個(gè)好處:
- API代碼層次更清晰
- API與調(diào)用程序代碼隔離
假設(shè)存在如下場景:需要做一個(gè)鏈接數(shù)據(jù)庫服務(wù)的模塊。提供一個(gè)connect函數(shù)用于鏈接。那么,在鏈接的過程中,就會(huì)發(fā)生以下幾種情況:
- socket連接超時(shí)
- socket拒絕連接
針對以上的情況,我們在模塊中定義幾個(gè)異常:
- # database.py
- class Error(Exception):
- """Root exception for all exceptions raised by this module."""
- class SocketTimeError(Error):
- pass
- class SocketRefuseError(Error):
- pass
- def connect():
- pass
調(diào)用API時(shí)異常捕獲的技巧
這樣在調(diào)用API的時(shí)候就可以這樣使用:
- try:
- connect()
- except SocketTimeError as err:
- log.error(err)
- except SocketRefuseError as err:
- log.error(err)
- except Error as err:
- log.error("API Unexpected error:%s" % err)
- except Exception:
- log.error("API bug cause exception.")
這樣精確定義多個(gè)異常,使得代碼層次清晰,增強(qiáng)了可讀性。值得注意的是:在代碼的***還捕獲了Error以及Exception兩個(gè)異常,這兩個(gè)操作分別對應(yīng)于可拓展性與健壯性的目的。
捕獲Root Exception以提高可拓展性:
我們知道,在實(shí)際鏈接數(shù)據(jù)庫時(shí),還可能會(huì)出現(xiàn)用戶沒有登陸權(quán)限等問題。所以,我們需要在下一個(gè)版本中加入PermissionDeny這個(gè)異常。但是,舊的調(diào)用代碼已經(jīng)寫好了,如果忘記修改的話,這個(gè)異??赡芫蜁?huì)無法被處理,進(jìn)而使得調(diào)用的程序奔潰。處于這樣的考慮,我們在調(diào)用API的時(shí)候,就應(yīng)該再捕獲API的Root Exception,即使之后新加入了其它的異常,在這一個(gè)except中也能被捕獲而不影響調(diào)用程序。使得API模塊的可拓展性得到了提高。
捕獲Exception以提高健壯性:
在調(diào)用API的時(shí)候,難免可能出現(xiàn)API內(nèi)部存在bug的情況。這個(gè)時(shí)候如果捕獲了Exception的話,就算API內(nèi)部因?yàn)閎ug發(fā)生了異常,也不會(huì)影響到調(diào)用程序的正常運(yùn)行。
從這兩點(diǎn)中可以看出,要達(dá)到這種效果,其實(shí)都要依賴于常規(guī)異常繼承于Exception類這個(gè)規(guī)矩。這樣的架構(gòu)劃分所帶來的好處是顯而易見的。
與異常相關(guān)的編程藝術(shù)
異常代替返回狀態(tài)碼
我們經(jīng)常需要編寫一些工具類的函數(shù),往往在這些函數(shù)的處理流程中,會(huì)產(chǎn)生很多的狀態(tài);而這些狀態(tài)也是調(diào)用者需要得到的信息。很多時(shí)候,會(huì)用一些具有意義的返回值來表示函數(shù)處理的狀態(tài)。
比如:
- def write(content):
- if isinstance(content, basestring):
- f_handler = open("file.txt", 'w')
- try:
- f_handler.write(context)
- except Exception:
- return -2 # write file fail
- else:
- return 0 # write file succcess
- finally:
- f_hanlder.close()
- else:
- return -1 # arg type error
調(diào)用代碼:
- result = write()
- if result == -1:
- log.error(u"type error")
- elif result = -2:
- log.error(u"write error")
- else:
- log.info("ok")
這種狀態(tài)碼的方式使用起來特別的不方便,調(diào)用者還需要去理解每個(gè)狀態(tài)碼的意義,帶來其它的學(xué)習(xí)成本;而且用if-else結(jié)構(gòu)也不易于后期的程序拓展。所以,我們可以使用觸發(fā)異常來代替返回狀態(tài)碼,每個(gè)異常名其實(shí)就包含了狀態(tài)的意義在內(nèi)(命名的藝術(shù)),使用起來也更好理解。
使用異常的方式:
- class Error(Exception):
- pass
- class OpenFileError(Error):
- pass
- class WriteContentError(Error):
- pass
- def write(content):
- if isinstance(content, basestring):
- f_handler = open("file.txt", 'w')
- try:
- f_handler.write(context)
- except Exception:
- raise WriteContentError
- finally:
- f_hanlder.close()
- else:
- raise OpenFileError
調(diào)用代碼:
- try:
- write()
- except OpenFileError as e:
- log.error(e)
- except WriteContentError as e:
- log.error(e)
- except Error:
- log.error("API Error")
- except Exception
- log.error("API Bug")
- else:
- log.info("ok")
結(jié)合上面一點(diǎn)提到的使用API時(shí)的異常捕獲,使得調(diào)用代碼變得更佳靈活。
異常處理與流程控制
錯(cuò)誤處理很重要,但如果它搞亂了代碼邏輯,就是錯(cuò)誤的做法
將異常處理與正常流程控制混為一談時(shí),代碼是十分丑陋的。我們應(yīng)該將二者分離,***的做法就是將異常代碼塊抽離到另外的函數(shù)中。
- try:
- action_a()
- action_b()
- action_c()
- except ActionException as e:
- log.error(e)
- else:
- action_d()
將異常處理分離:
- def action_executor():
- action_a()
- action_b()
- action_c()
- def action():
- try:
- action_executor()
- except ActionException as e:
- log.error(e)
- action()
- action_d()