深入理解Python異常處理的哲學(xué)
所謂異常指的是程序的執(zhí)行出現(xiàn)了非預(yù)期行為,就好比現(xiàn)實(shí)中的做一件事過程中總會出現(xiàn)一些意外的事。異常的處理是跨越編程語言的,和具體的編程細(xì)節(jié)相比,程序執(zhí)行異常的處理更像是哲學(xué)。限于認(rèn)知能力和經(jīng)驗(yàn)所限,不可能達(dá)到像解釋器下import this看到的python設(shè)計之禪一樣,本文就結(jié)合實(shí)際使用簡單的聊一聊。
0. 前言
工作中,程序員之間一言不合就亮代碼,畢竟不管是代碼本身還是其執(zhí)行過程,不會存在二義性,更不會含糊不清,代碼可謂是程序員之間的官方語言。但是其處理問題的邏輯或者算法則并非如此。
讓我至今記憶猶新的兩次程序員論劍有:
反問一:項目后期所有的異常處理都要去掉,不允許上線后出現(xiàn)未知的異常,把你這里的異常處理去掉,換成if else;
反問二:這里為什么要進(jìn)行異常處理?代碼都是你寫的,怎么會出現(xiàn)異常呢?
這是我親身經(jīng)歷的,不知道大家碰到這兩個問題會怎樣回答,至少我當(dāng)時竟無言以對。這兩個問題分別在不同的時間針對不同的問題出自一個互聯(lián)網(wǎng)巨頭中某個資深QA和資深開發(fā)的反問。
暫且不論對錯,畢竟不同人考慮問題的出發(fā)點(diǎn)是不同的。但是從這么堅決的去異常處理的回答中至少有一點(diǎn)可以肯定,那就是很多人對自己的代碼太過自信或者說是察覺代碼潛在問題的直覺力不夠,更別提正確的處理潛在的問題以保證重要業(yè)務(wù)邏輯的處理流程。寫代碼的時候如果只簡單考慮正常的情況,那是在往代碼中下毒。
接下類本篇博文將按照套路出牌(避免被Ctrl + W),介紹一下python的異常處理的概念和具體操作.
1. 為什么要異常處理
常見的程序bug無非就兩大類:
- 語法錯誤;
- 邏輯不嚴(yán)謹(jǐn)或者思維混亂導(dǎo)致的邏輯錯誤;
顯然第二種錯誤更難被發(fā)現(xiàn),且后果往往更嚴(yán)重。無論哪一種bug,有兩種后果等著我們:一、程序崩掉;二、執(zhí)行結(jié)果不符合預(yù)期;
對于一些重要關(guān)鍵的執(zhí)行操作,異常處理可以控制程序在可控的范圍執(zhí)行,當(dāng)然前提是正確的處理。
比如我們給第三方提供的API或者使用第三方提供的API。多數(shù)情況下要正確的處理調(diào)用者錯誤的調(diào)用參數(shù)和返回異常結(jié)果的情況,不然就可能要背黑鍋了。
在不可控的環(huán)境中運(yùn)行程序,異常處理是必須的。然而困難的地方是當(dāng)異常發(fā)生時,如何進(jìn)行處理。
2. python異常處理
下面逐步介紹一下python異常處理相關(guān)的概念。
2.1 異常處理結(jié)構(gòu)
必要的結(jié)構(gòu)為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 內(nèi)置異常類型
模塊exceptions中包含了所有內(nèi)置異常類型,類型的繼承關(guān)系如下:
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子句的常用的寫法如下:
- except: # 默認(rèn)捕獲所有類型的異常
- except Exception Class: # 捕獲Exception Class類型的異常
- except Exception Class as e: # 捕獲Exception Class類型的異常,異常對象賦值到e
- except (Exception Class1, Exception Class2, ...) as e: # 捕獲列表中任意一種異常類型
上面的異常類可以是下面python內(nèi)置異常類型,也可以是自定義的異常類型。
2.4 異常匹配原則
- 所有except子句按順序一一匹配,匹配成功則忽略后續(xù)的except子句;
- 若拋出異常對象為except子句中給出的異常類型的對象或給出的異常類型的派生類對象,則匹配成功;
- 如果所有的except子句均匹配失敗,異常會向上傳遞;
- 如果依然沒有被任何try...except捕獲到,程序在終止前會調(diào)用sys.excepthook進(jìn)行處理;
2.5 else & finally
如果沒有異常發(fā)生,且存在else子句,則執(zhí)行else子句。只要存在finally子句,無論任何情況下都會被執(zhí)行。
可能唯一不好理解的地方就是finally。沒有異常、捕獲異常、異常上傳以及異常處理過程中發(fā)生異常等均會執(zhí)行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'
分別輸入?yún)?shù)(1, 2),(1, 0)和 (1,“0”)執(zhí)行:
print 'return value: %s' % division(a, b)
得到的結(jié)果如下:
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'
可以看到縱使程序發(fā)生異常且沒有被正確處理,在程序終止前,finally語句依舊被執(zhí)行了。可以將此看做程序安全的最后一道有效屏障。主要進(jìn)行一些善后清理工作,比如資源釋放、斷開網(wǎng)絡(luò)連接等。當(dāng)然with聲明可以自動幫我們進(jìn)行一些清理工作。
2.6 raise拋出異常
程序執(zhí)行過程中可以使用raise主動的拋出異常.
try: e = Exception('Hello', 'World') e.message = 'Ni Hao!' raise e except Exception as inst: print type(inst), inst, inst.args, inst.message
結(jié)果:<type 'exceptions.Exception'> ('Hello', 'World') ('Hello', 'World') Ni Hao!
上面展示了except對象的屬性args, message。
2.7 自定義異常
絕大部分情況下內(nèi)置類型的異常已經(jīng)能夠滿足平時的開發(fā)使用,如果想要自定義異常類型,可以直接繼承內(nèi)置類型來實(shí)現(xiàn)。
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
自定義異常應(yīng)該直接繼承自Exception類或其子類,而不要繼承自BaseException.
3. Stack Trace
python執(zhí)行過程中發(fā)生異常,會告訴我們到底哪里出現(xiàn)問題和什么問題。這兩種類型的錯誤信息分別為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
上面的錯誤信息包含錯誤發(fā)生時當(dāng)前的堆棧信息(stack trace, 前三行)和異常信息(exception,最后一行),分別存放在traceback objects和拋出的異常對象中。
異常對象及異常信息前面已經(jīng)介紹過,接下來我們在看一下異常發(fā)生時,stack trace的處理。
Traceback objects represent a stack trace of an exception. A traceback object is created when an exception occurs.
這時有兩種情況:
- 異常被try...except捕獲
- 沒有被捕獲或者干脆沒有處理
正常的代碼執(zhí)行過程,可以使用traceback.print_stack()輸出當(dāng)前調(diào)用過程的堆棧信息。
3.1 捕獲異常
對于第一種情況可以使用下面兩種方式獲取stack trace信息:
trace_str = traceback.format_exc()
或者從sys.exc_info()中獲取捕獲的異常對象等的信息,然后格式化成trace信息。
def get_trace_str(self): """ 從當(dāng)前棧幀或者之前的棧幀中獲取被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結(jié)構(gòu)中的except Exception class as e中獲取。
3.2 未捕獲異常
第二種情況,如果異常沒有被處理或者未被捕獲則會在程序推出前調(diào)用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函數(shù)來取代sys.excepthook函數(shù)。在hook函數(shù)中根據(jù)異常類型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處理流程結(jié)果:
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捕獲異常流程,結(jié)果如下:
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. 異常信息收集
講了這么多,我們看一下如何實(shí)現(xiàn)一個程序中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): """ 從當(dāng)前棧幀或者之前的棧幀中獲取被except捕獲的異常信息; 沒有被try except捕獲的異常會自動使用handle_traceback進(jìn)行收集 """ t, v, tb = sys.exc_info() self.handle_traceback(t, v, tb, False) def handle_traceback(self, t, v, tb, is_hook = True): """ 將此函數(shù)替換sys.excepthook以能夠自動收集沒有被try...except捕獲的異常, 使用try except處理的異常需要手動調(diào)用上面的函數(shù)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'
結(jié)果用兩種方式收集到兩個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'
可以將標(biāo)準(zhǔn)的輸入和輸出重定向,將打印日志和錯誤信息輸入到文件中:
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的收集主要用到兩點(diǎn):如何捕獲異常和兩種情況下異常信息的收集,前面都介紹過。
5. 總結(jié)
python 異常處理:
- 使用對象來表示異常錯誤信息,每種異常均有一種對應(yīng)的類,BaseException為所有表示異常處理類的基類。
- 程序執(zhí)行過程中拋出的異常會匹配該對象對應(yīng)的異常類和其所有的基類。
- 可以從內(nèi)置類型的異常類派生出自定義的異常類。
- 被捕獲的異??梢栽俅伪粧伋觥?/li>
- 可以的話盡量使用內(nèi)置的替代方案,如if getattr(obj, attr_name, None),或者with結(jié)構(gòu)等。
- sys.exc_info()保存當(dāng)前棧幀或者之前的棧幀中獲取被try, except捕獲的異常信息。
- 未處理的異常導(dǎo)致程序終止前會被sys.excpethook處理,可以自定義定義sys.excpethook。
異常的陷阱:
正確的異常處理能讓代碼有更好的魯棒性,但是錯誤的使用異常會過猶不及。
捕獲異常卻忽略掉或者錯誤的處理是不可取的。濫用異常處理不僅達(dá)不到提高系統(tǒng)穩(wěn)定性的效果,還會隱藏掉引起錯誤的誘因,導(dǎo)致排查問題的難度增加。
因此比如何捕獲異常更重要的是,異常發(fā)生時應(yīng)當(dāng)如何處理。
好了,以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
django多文件上傳,form提交,多對多外鍵保存的實(shí)例
今天小編就為大家分享一篇django多文件上傳,form提交,多對多外鍵保存的實(shí)例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-08-08python 讀取數(shù)據(jù)庫并繪圖的實(shí)例
今天小編就為大家分享一篇python 讀取數(shù)據(jù)庫并繪圖的實(shí)例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-12-12Python實(shí)現(xiàn)刪除文件中含“指定內(nèi)容”的行示例
這篇文章主要介紹了Python實(shí)現(xiàn)刪除文件中含“指定內(nèi)容”的行功能,涉及Python針對文件讀取及字符串遍歷、判斷等相關(guān)操作技巧,需要的朋友可以參考下2017-06-06關(guān)于WARNING:Ignoring?invalid?distribution?-pencv-python....
這篇文章主要給大家介紹了關(guān)于WARNING:Ignoring?invalid?distribution?-pencv-python....警告信息的處理方法,文中通過圖文將解決的辦法介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用python具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2023-03-03

pandas實(shí)現(xiàn)excel中的數(shù)據(jù)透視表和Vlookup函數(shù)功能代碼

python的time模塊和datetime模塊實(shí)例解析

python web應(yīng)用程序之Django數(shù)據(jù)庫詳解

詳解Python如何利用pdfplumber提取PDF中的表格