您好,登錄后才能下訂單哦!
所謂異常指的是程序的執行出現了非預期行為,就好比現實中的做一件事過程中總會出現一些意外的事。異常的處理是跨越編程語言的,和具體的編程細節相比,程序執行異常的處理更像是哲學。限于認知能力和經驗所限,不可能達到像解釋器下import this看到的python設計之禪一樣,本文就結合實際使用簡單的聊一聊。
0. 前言
工作中,程序員之間一言不合就亮代碼,畢竟不管是代碼本身還是其執行過程,不會存在二義性,更不會含糊不清,代碼可謂是程序員之間的官方語言。但是其處理問題的邏輯或者算法則并非如此。
讓我至今記憶猶新的兩次程序員論劍有:
反問一:項目后期所有的異常處理都要去掉,不允許上線后出現未知的異常,把你這里的異常處理去掉,換成if else;
反問二:這里為什么要進行異常處理?代碼都是你寫的,怎么會出現異常呢?
這是我親身經歷的,不知道大家碰到這兩個問題會怎樣回答,至少我當時竟無言以對。這兩個問題分別在不同的時間針對不同的問題出自一個互聯網巨頭中某個資深QA和資深開發的反問。
暫且不論對錯,畢竟不同人考慮問題的出發點是不同的。但是從這么堅決的去異常處理的回答中至少有一點可以肯定,那就是很多人對自己的代碼太過自信或者說是察覺代碼潛在問題的直覺力不夠,更別提正確的處理潛在的問題以保證重要業務邏輯的處理流程。寫代碼的時候如果只簡單考慮正常的情況,那是在往代碼中下毒。
接下類本篇博文將按照套路出牌(避免被Ctrl + W),介紹一下python的異常處理的概念和具體操作.
1. 為什么要異常處理
常見的程序bug無非就兩大類:
顯然第二種錯誤更難被發現,且后果往往更嚴重。無論哪一種bug,有兩種后果等著我們:一、程序崩掉;二、執行結果不符合預期;
對于一些重要關鍵的執行操作,異常處理可以控制程序在可控的范圍執行,當然前提是正確的處理。
比如我們給第三方提供的API或者使用第三方提供的API。多數情況下要正確的處理調用者錯誤的調用參數和返回異常結果的情況,不然就可能要背黑鍋了。
在不可控的環境中運行程序,異常處理是必須的。然而困難的地方是當異常發生時,如何進行處理。
2. python異常處理
下面逐步介紹一下python異常處理相關的概念。
2.1 異常處理結構
必要的結構為try ... except,至少有一個except,else 和 finally 可選。
try: code blocks except (Exception Class1, Exception Class2, ...) as e: catch and process exception except Exception ClassN: catch and process exception ... ... else: when nothing unexpected happened finally: always executed when all to end
2.2 python 內置異常類型
模塊exceptions中包含了所有內置異常類型,類型的繼承關系如下:
BaseException +-- SystemExit +-- KeyboardInterrupt +-- GeneratorExit +-- Exception +-- StopIteration +-- StandardError | +-- BufferError | +-- ArithmeticError | | +-- FloatingPointError | | +-- OverflowError | | +-- ZeroDivisionError | +-- AssertionError | +-- AttributeError | +-- EnvironmentError | | +-- IOError | | +-- OSError | | +-- WindowsError (Windows) | | +-- VMSError (VMS) | +-- EOFError | +-- ImportError | +-- LookupError | | +-- IndexError | | +-- KeyError | +-- MemoryError | +-- NameError | | +-- UnboundLocalError | +-- ReferenceError | +-- RuntimeError | | +-- NotImplementedError | +-- SyntaxError | | +-- IndentationError | | +-- TabError | +-- SystemError | +-- TypeError | +-- ValueError | +-- UnicodeError | +-- UnicodeDecodeError | +-- UnicodeEncodeError | +-- UnicodeTranslateError +-- Warning +-- DeprecationWarning +-- PendingDeprecationWarning +-- RuntimeWarning +-- SyntaxWarning +-- UserWarning +-- FutureWarning +-- ImportWarning +-- UnicodeWarning +-- BytesWarning
2.3 except clause
excpet子句的常用的寫法如下:
上面的異常類可以是下面python內置異常類型,也可以是自定義的異常類型。
2.4 異常匹配原則
2.5 else & finally
如果沒有異常發生,且存在else子句,則執行else子句。只要存在finally子句,無論任何情況下都會被執行。
可能唯一不好理解的地方就是finally。沒有異常、捕獲異常、異常上傳以及異常處理過程中發生異常等均會執行finally語句。
下面看個例子:
def division(a, b): try: print'res = %s' % (a / b) except (ZeroDivisionError, ArithmeticError) as e: return str(e) # 注意此處使用的是return else: print '%s / %s = %s' % (a, b, a / b) finally: print 'finally clause'
分別輸入參數(1, 2),(1, 0)和 (1,“0”)執行:
print 'return value: %s' % division(a, b)
得到的結果如下:
res = 0
/ 2 = 0
finally clause
return value: Nonefinally clause
return value: integer division or modulo by zerofinally clause
Traceback (most recent call last):
File "D:\My Folders\Cnblogs\Alpha Panda\Main.py", line 217, in <module>
print 'return value: %s' % division(1, "0")
File "D:\My Folders\Cnblogs\Alpha Panda\Main.py", line 208, in division
print'res = %s' % (a / b)
TypeError: unsupported operand type(s) for /: 'int' and 'str'
可以看到縱使程序發生異常且沒有被正確處理,在程序終止前,finally語句依舊被執行了。可以將此看做程序安全的最后一道有效屏障。主要進行一些善后清理工作,比如資源釋放、斷開網絡連接等。當然with聲明可以自動幫我們進行一些清理工作。
2.6 raise拋出異常
程序執行過程中可以使用raise主動的拋出異常.
try: e = Exception('Hello', 'World') e.message = 'Ni Hao!' raise e except Exception as inst: print type(inst), inst, inst.args, inst.message
結果:<type 'exceptions.Exception'> ('Hello', 'World') ('Hello', 'World') Ni Hao!
上面展示了except對象的屬性args, message。
2.7 自定義異常
絕大部分情況下內置類型的異常已經能夠滿足平時的開發使用,如果想要自定義異常類型,可以直接繼承內置類型來實現。
class ZeroDivZeroError(ZeroDivisionError): def __init__(self, value): self.value = value def __str__(self): return repr(self) def __repr__(self): return self.value try: # do something and find 0 / 0 raise ZeroDivZeroError('hahajun') except ZeroDivZeroError as err: print 'except info %s' % err
自定義異常應該直接繼承自Exception類或其子類,而不要繼承自BaseException.
3. Stack Trace
python執行過程中發生異常,會告訴我們到底哪里出現問題和什么問題。這兩種類型的錯誤信息分別為stack trace和 exception,在程序中分別用traceback object和異常對象表示。
Traceback (most recent call last):
File "D:\My Folders\Cnblogs\Alpha Panda\Main.py", line 270, in <module>
1 / 0
ZeroDivisionError: integer division or modulo by zero
上面的錯誤信息包含錯誤發生時當前的堆棧信息(stack trace, 前三行)和異常信息(exception,最后一行),分別存放在traceback objects和拋出的異常對象中。
異常對象及異常信息前面已經介紹過,接下來我們在看一下異常發生時,stack trace的處理。
Traceback objects represent a stack trace of an exception. A traceback object is created when an exception occurs.
這時有兩種情況:
正常的代碼執行過程,可以使用traceback.print_stack()輸出當前調用過程的堆棧信息。
3.1 捕獲異常
對于第一種情況可以使用下面兩種方式獲取stack trace信息:
trace_str = traceback.format_exc()
或者從sys.exc_info()中獲取捕獲的異常對象等的信息,然后格式化成trace信息。
def get_trace_str(self): """ 從當前棧幀或者之前的棧幀中獲取被except捕獲的異常信息; 沒有被try except捕獲的異常會直接傳遞給sys.excepthook """ t, v, tb = sys.exc_info() trace_info_list = traceback.format_exception(t, v, tb) trace_str = ' '.join(trace_info_list)
至于拋出的包含異常信息的異常對象則可以在try...except結構中的except Exception class as e中獲取。
3.2 未捕獲異常
第二種情況,如果異常沒有被處理或者未被捕獲則會在程序推出前調用sys.excepthook將traceback和異常信息輸出到sys.stderr。
def except_hook_func(tp, val, tb): trace_info_list = traceback.format_exception(tp, val, tb) trace_str = ' '.join(trace_info_list) print 'sys.excepthook' print trace_str sys.excepthook = except_hook_func
上面自定義except hook函數來取代sys.excepthook函數。在hook函數中根據異常類型tp、異常值和traceback對象tb獲取stack trace。這種情況下不能從sys.exc_info中獲取異常信息。
3.3 測試
def except_hook_func(tp, val, tb): trace_info_list = traceback.format_exception(tp, val, tb) trace_str = ' '.join(trace_info_list) print 'sys.excepthook' print trace_str sys.excepthook = except_hook_func try: / 0 except TypeError as e: res = traceback.format_exc() print "try...except" print str(e.message) print res
走的是sys.excepthook處理流程結果:
sys.excepthook
Traceback (most recent call last):
File "D:\My Folders\Cnblogs\Alpha Panda\Main.py", line 259, in <module>
1 / 0
ZeroDivisionError: integer division or modulo by zero
將except TypeError as e 改為 except ZeroDivisionError as e,則走的是try...except捕獲異常流程,結果如下:
try...except
integer division or modulo by zero
Traceback (most recent call last):
File "D:\My Folders\Cnblogs\Alpha Panda\Main.py", line 259, in <module>
1 / 0
ZeroDivisionError: integer division or modulo by zero
4. 異常信息收集
講了這么多,我們看一下如何實現一個程序中trace信息的收集。
class TracebackMgr(object): def _get_format_trace_str(self, t, v, tb): _trace = traceback.format_exception(t, v, tb) return ' '.join(_trace) def handle_one_exception(self): """ 從當前棧幀或者之前的棧幀中獲取被except捕獲的異常信息; 沒有被try except捕獲的異常會自動使用handle_traceback進行收集 """ t, v, tb = sys.exc_info() self.handle_traceback(t, v, tb, False) def handle_traceback(self, t, v, tb, is_hook = True): """ 將此函數替換sys.excepthook以能夠自動收集沒有被try...except捕獲的異常, 使用try except處理的異常需要手動調用上面的函數handle_one_exception才能夠收集 """ trace_str = self._get_format_trace_str(t, v, tb) self.record_trace(trace_str, is_hook) # do something else def record_trace(self, trace_str, is_hook): # Do somethind print 'is_hook: %s' % is_hook print trace_str
其用法很簡單:
trace_mgr = TracebackMgr() sys.excepthook = trace_mgr.handle_traceback try: / 0 except Exception as e: trace_mgr.handle_one_exception() # process trace / '0'
結果用兩種方式收集到兩個trace信息:
is_hook: False
Traceback (most recent call last):
File "D:\My Folders\Cnblogs\Alpha Panda\Main.py", line 299, in <module>
/ 0
ZeroDivisionError: integer division or modulo by zerois_hook: True
Traceback (most recent call last):
File "D:\My Folders\Cnblogs\Alpha Panda\Main.py", line 304, in <module>
/ '0'
TypeError: unsupported operand type(s) for /: 'int' and 'str'
可以將標準的輸入和輸出重定向,將打印日志和錯誤信息輸入到文件中:
class Dumpfile(object): @staticmethod def write(str_info): with open('./dump_file.txt', 'a+') as fobj: fobj.write(str_info) def flush(self): self.write('') sys.stdout = sys.stderr = Dumpfile()
trace的收集主要用到兩點:如何捕獲異常和兩種情況下異常信息的收集,前面都介紹過。
5. 總結
python 異常處理:
異常的陷阱:
正確的異常處理能讓代碼有更好的魯棒性,但是錯誤的使用異常會過猶不及。
捕獲異常卻忽略掉或者錯誤的處理是不可取的。濫用異常處理不僅達不到提高系統穩定性的效果,還會隱藏掉引起錯誤的誘因,導致排查問題的難度增加。
因此比如何捕獲異常更重要的是,異常發生時應當如何處理。
好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。