Python異常編程技巧

編程中常常會須要使用到異常處理的狀況,在閱讀了一些資料後,整理了關於異常處理的一些小技巧記錄以下。html

如何自定義異常

定義異常類

在實際編程中,有時會發現Python提供的內建異常的不夠用,咱們須要在特殊業務場景下的異常。這時就須要咱們來定義本身的異常。按照Python約定俗成的習慣,用戶定義的異常通常都是繼承於Exception類,由它開始拓展。後面咱們能夠看到這樣作在捕獲異常的時候會帶來很大的便利。python

>>> 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異常相關的技巧

API的異常分爲定義異常與調用API時如何捕獲異常兩個部分,這兩者相輔相成。數據庫

定義API異常的技巧

在本身編寫API的時候,應該定義Root Exception——API中的根異常,其它異常都繼承於它。這樣的作法有兩個好處:編程

  1. API代碼層次更清晰架構

  2. API與調用程序代碼隔離socket

假設存在以下場景:須要作一個連接數據庫服務的模塊。提供一個connect函數用於連接。那麼,在連接的過程當中,就會發生如下幾種狀況:函數

  • socket鏈接超時工具

  • socket拒絕鏈接學習

針對以上的狀況,咱們在模塊中定義幾個異常:this

# 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

調用API時異常捕獲的技巧

這樣在調用API的時候就能夠這樣使用:

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.")

這樣精肯定義多個異常,使得代碼層次清晰,加強了可讀性。值得注意的是:在代碼的最後還捕獲了Error以及Exception兩個異常,這兩個操做分別對應於可拓展性與健壯性的目的。

捕獲Root Exception以提升可拓展性:

咱們知道,在實際連接數據庫時,還可能會出現用戶沒有登錄權限等問題。因此,咱們須要在下一個版本中加入PermissionDeny這個異常。可是,舊的調用代碼已經寫好了,若是忘記修改的話,這個異常可能就會沒法被處理,進而使得調用的程序奔潰。處於這樣的考慮,咱們在調用API的時候,就應該再捕獲API的Root Exception,即便以後新加入了其它的異常,在這一個except中也能被捕獲而不影響調用程序。使得API模塊的可拓展性獲得了提升。

捕獲Exception以提升健壯性:

在調用API的時候,不免可能出現API內部存在bug的狀況。這個時候若是捕獲了Exception的話,就算API內部由於bug發生了異常,也不會影響到調用程序的正常運行。

從這兩點中能夠看出,要達到這種效果,其實都要依賴於常規異常繼承於Exception類這個規矩。這樣的架構劃分所帶來的好處是顯而易見的。

與異常相關的編程藝術

異常代替返回狀態碼

咱們常常須要編寫一些工具類的函數,每每在這些函數的處理流程中,會產生不少的狀態;而這些狀態也是調用者須要獲得的信息。不少時候,會用一些具備意義的返回值來表示函數處理的狀態。
好比:

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

調用代碼:

result = write()
if result == -1:
    log.error(u"type error")
elif result = -2:
    log.error(u"write error")
else:
    log.info("ok")

這種狀態碼的方式使用起來特別的不方便,調用者還須要去理解每一個狀態碼的意義,帶來其它的學習成本;並且用if-else結構也不易於後期的程序拓展。因此,咱們可使用觸發異常來代替返回狀態碼,每一個異常名其實就包含了狀態的意義在內(命名的藝術),使用起來也更好理解。

使用異常的方式:

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

調用代碼:

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")

結合上面一點提到的使用API時的異常捕獲,使得調用代碼變得更佳靈活。

異常處理與流程控制

錯誤處理很重要,但若是它搞亂了代碼邏輯,就是錯誤的作法

將異常處理與正常流程控制混爲一談時,代碼是十分醜陋的。咱們應該將兩者分離,最好的作法就是將異常代碼塊抽離到另外的函數中。

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()

參考資料

相關文章
相關標籤/搜索