在shell腳本中,經常使用if來判斷程序的某個部分是否可能會出錯,並在if的分支中作出對應的處理,從而讓程序更具健壯性。if判斷是異常處理的一種方式,全部語言都通用。對於特性完整的編程語言來講,都有專門的異常處理機制,有些語言用起來可能會很複雜,要求一堆堆的,有些語言則很是簡潔,用起來很是通暢。html
對於索引查找的操做,在索引越界搜索的時候會報錯。例如:python
>>> s="long" >>> s[4] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: string index out of range
所報的錯誤是IndexError。若是將索引查找放在一個函數裏:shell
>>> def fetcher(obj,index): ... return obj[index]
那麼調用函數的時候,若是裏面的索引越界了,異常將彙報到函數調用者。編程
>>> fetcher(s,3) 'g' >>> fetcher(s,4) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 2, in fetcher IndexError: string index out of range
可使用try/except來捕獲異常。做爲入門示例,下面是簡單版的格式:json
try: statement1 ... statementN except <ERRORTYPE>: ...statementS...
例如,try中是要監視正確執行與否的語句,ERRORTYPE是要監視的錯誤類型。less
例如捕獲上面的函數調用:編程語言
def fetcher(obj, index): return obj[index] s = "long" try: print(fetcher(s, 3) * 4) print(fetcher(s, 4) * 4) except IndexError: print("something wrong") print("after Exception, Continue")
輸出結果:ide
gggg something wrong after Exception, Continue
由於上面的fetcher(s, 4)
會拋出異常,且正好匹配except監視的異常類型,因此輸出something wrong
,異常被處理以後,程序繼續執行,即try/except後面的print()。函數
finally是try以後必定會執行的語句段落。能夠結合except一塊兒使用。測試
try: statement1 ... statementN finally: ...statementF...
try: statement1 ... statementN except <ERRORTYPE>: ...statementS... finally: ...statementF...
不論try中的語句是否出現異常,不論except是否捕獲到對應的異常,finally都會執行:
通常來講,finally中都會用來作程序善後清理工做。
例如:
def fetcher(obj, index): return obj[index] s = "long" try: print(fetcher(s, 3) * 4) print(fetcher(s, 4) * 4) except IndexError: print("something wrong") finally: print("in finally") print("after Exception, Continue")
輸出:
gggg something wrong in finally after Exception, Continue
若是把except那段代碼刪掉,獲得的結果將是:
gggg in finally # 輸出了finally的內容 Traceback (most recent call last): File "g:/pycode/list.py", line 8, in <module> print(fetcher(s, 4) * 4) File "g:/pycode/list.py", line 2, in fetcher return obj[index] IndexError: string index out of range
使用raise或assert能夠主動生成異常狀況。其中raise能夠直接拋出某個異常,assert須要經過布爾值來判斷,而後再拋出給定的錯誤。
例如,在函數裏作個沒什麼用的的判斷,用來演示raise:
def fetcher(obj, index): if index >= len(obj): raise IndexError return obj[index]
這和直接索引越界是以同樣的。上面raise拋出的異常IndexError是一個內置異常,能夠直接引用這些內置異常。稍後會演示如何自定義本身的異常。
拋出異常後,就能夠按照前面介紹的try來處理異常。
assert是一種斷言,在計算機語言中表示:若是斷言條件爲真就跳過,若是爲假就拋出異常信息。它能夠自定義異常信息。
例如:
def fetcher(obj, index): assert index < len(obj), "one exception" return obj[index]
不少時候會直接在assert中使用False、True的布爾值進行程序的調試。
assert True, "assert not hit" assert False, "assert hit"
python中的異常是經過類來定義的,並且全部的異常類都繼承自Exception類,而Exception又繼承自BaseException(這個類不能直接做爲其它異常類的父類)。因此自定義異常的時候,也要繼承Exception,固然,繼承某個中間異常類也能夠。
例如,定義索引越界的異常類,注意這個類中直接pass,但由於繼承了Exception,它仍然會有異常信息。
class MyIndexError(Exception): pass
例如,判斷字母是不是大寫,若是是,就拋異常:
def fetcher(obj,index): if index >= len(obj): raise MyIndexError return obj[index]
測試一下:
s = "long" print(fetcher(s, 3) * 4) print(fetcher(s, 4) * 4)
結果:
gggg Traceback (most recent call last): File "g:/pycode/list.py", line 12, in <module> print(fetcher(s, 4) * 4) File "g:/pycode/list.py", line 6, in fetcher raise MyIndexError __main__.MyIndexError
須要注意,由於異常類都繼承字Exception,except監視Exception異常的時候,也會匹配其它的異常。更標準地說,監視異常父類,也會捕獲到這個類的子類異常。
看異常信息是最基本的能力。例如,下面的這段代碼會報除0錯誤:
def a(x, y): return x/y def b(x): print(a(x, 0)) b(1)
執行時,報錯信息以下:
Traceback (most recent call last): File "g:/pycode/list.py", line 7, in <module> b(1) File "g:/pycode/list.py", line 5, in b print(a(x, 0)) File "g:/pycode/list.py", line 2, in a return x/y ZeroDivisionError: division by zero
這個堆棧跟蹤信息中已經明確說明了(most recent call last)
,說明最近產生異常的調用在最上面,也就是第7行。上面的整個過程是這樣的:第7行出錯,它是由於第5行的代碼引發的,而第5行之因此錯是第2行的源代碼引發的。
因此,從最底部能夠看到最終是由於什麼而拋出異常,從最頂部能夠看到是執行到哪一句出錯。
try: <statements> except <name1>: # 捕獲到名爲name1的異常 <statements> except (name2, name3): # 捕獲到name2或name3任一異常 <statements> except <name4> as <data>: # 捕獲name4異常,並獲取異常的示例 <statements> except: # 以上異常都不匹配時 <statements> else: # 沒有產生異常時 <statements> finally: # 必定會執行的 <statements>
注意,當拋出的異常沒法被匹配時,將歸類於空的except:
,但這是很危險的行爲,由於不少時候的異常是必然的,好比某些退出操做、內存不足、Ctrl+C等等,而這些都會被捕獲。與之大體等價的是捕獲異常類的"僞"祖先類Exception
,即except Exception:
,它和空異常匹配相似,但能解決很多不該該匹配的異常。但使用Exception依然是危險的,能不用盡可能不用。
若是一個異常既能被name1匹配,又能被name2匹配,則先匹配到的處理這個異常。
經過as關鍵字能夠將except捕獲到的異常對象賦值給data變量。用法稍後會解釋,如今須要知道的是,在python 3.x中,變量data只在當前的except塊範圍內有效,出了範圍就會被回收。若是想要保留異常對象,能夠將data賦值給一個變量。例以下面的b在出了try範圍都有效,可是a在這個except以後就無效了。
except Exception as a: print(a) b=a
經過else分句能夠知道,這段try代碼中沒有出現任何異常。不然就不會執行到else分句。
raise用於手動觸發一個異常。而每一種異常都是一個異常類,因此觸發其實是觸發一個異常類的實例對象。
raise <instance> # 直接觸發一個異常類的對象 raise <class> # 構建此處所給類的一個異常對象並觸發 raise # 觸發最近觸發的異常 raise <2> from <1> # 將<1>的異常附加在<2>上
其中第二種形式,raise會根據給定類不傳遞任何參數地自動構建一個異常對象,並觸發這個異常對象。第三種直接觸發最近觸發的異常對象,這在傳播異常的時候頗有用。
例如,下面兩種方式其實是等價的,只不過第一種方式傳遞的是類,raise會隱式地自動建立這個異常類的實例對象。
raise IndexError raise IndexError()
能夠爲異常類構建實例時指定點參數信息,這些參數會保存到名爲args的元組。例如:
try: raise IndexError("something wrong") except Exception as E: print(E.args)
輸出:
('something wrong',)
不只如此,只要是異常類或異常對象,無論它們的存在形式如何,均可以放在raise中。例如:
err = IndexErro() raise err errs = [IndexError, TypeError] raise errs[0]
對於第三種raise形式,它主要用來傳播異常,通常用在except代碼段中。例如:
try: raise IndexError("aaaaa") except IndexError: print("something wrong") raise
由於異常被except捕獲後,就表示這個異常已經處理過了,程序會跳轉到finally或整個try塊的尾部繼續執行下去。可是若是不想讓程序繼續執行,而是僅僅只是想知道發生了這個異常,並作一番處理,而後繼續向上觸發異常。這就是異常傳播。
由於實際觸發的異常都是類的實例對象,因此它有屬性。並且,能夠經過在except中使用as來將對象賦值給變量:
try: 1/0 except Exception as a: print(a)
變量a在出了except的範圍就失效,因此能夠將它保留給一個不會失效的變量:
try: 1/0 except Exception as a: print(a) b=a print(b)
若是在一個except中觸發了另外一個異常,會形成異常鏈:
try: 1/0 except Exception as E: raise TypeError('Bad')
將會報告兩個異常,並提示處理異常E的時候,觸發了另外一個異常TypeError。
Traceback (most recent call last): File "g:/pycode/list.py", line 2, in <module> 1/0 ZeroDivisionError: division by zero During handling of the above exception, another exception occurred: Traceback (most recent call last): File "g:/pycode/list.py", line 4, in <module> raise TypeError('Bad') TypeError: Bad
使用from關鍵字,可讓關係更加明確。
try: 1/0 except Exception as E: raise TypeError('Bad') from E
下面是錯誤報告:
Traceback (most recent call last): File "g:/pycode/list.py", line 2, in <module> 1/0 ZeroDivisionError: division by zero The above exception was the direct cause of the following exception: Traceback (most recent call last): File "g:/pycode/list.py", line 4, in <module> raise TypeError('Bad') from E TypeError: Bad
實際上,使用from關鍵字的時候,會將E的異常對象附加到TypeError的__cause__
屬性上。
但不管如何,這裏都觸發了多個異常。在python 3.3版本,可使用from None
的方式來掩蓋異常的來源,也就是禁止輸出異常E,中止異常鏈:
try: 1/0 except Exception as E: raise TypeError('Bad') from None
錯誤報告以下:
Traceback (most recent call last): File "g:/pycode/list.py", line 4, in <module> raise TypeError('Bad') from None TypeError: Bad
可見,異常信息中少了不少內容。
assert斷言經常使用於調試。用法以下:
assert test, data
它實際上等價因而條件判斷的raise。它等價於下面的方式:
if __debug__: if not test: raise AssertionError(data)
若是條件test的測試爲真,就跳過,不然就拋出異常。這個異常是經過AssertionError類構造的,構造異常對象的參數是data。data會放進名爲args的元組屬性中。
try: assert False,"something wrong" except Exception as E: print(E.args)
一樣,assert產生的是名爲AssertionError的異常,若是不捕獲這個AssertionError異常,程序將會終止。
除了調試,assert還偶爾用來判斷必要的條件,不知足條件就異常,以便讓程序更加健壯。例如:
def f(x): assert x >= 0, "x must great or equal than 0" return x ** 2 print(f(2)) print(f(0)) print(f(-2)) # 觸發AssertionError異常
須要注意的是,寫assert的測試條件時,測試結果爲假才觸發異常。因此,應該以if not true的角度去考慮條件,或者以unless的角度考慮。或者說,後面觸發的異常信息,和測試條件應該是正相關的,例如示例中異常信息的說法是x必須大於等於0,和測試條件x >= 0
是正相關的。
assert還經常使用於父類方法的某些方法中,這些方法要求子類必須重寫父類的方法。因而:
class cls: ... def f(self): assert False, "you must override method: f"
此外,assert不該該用來觸發那些python早已經定義好的異常。例如索引越界、類型錯誤等等。這些python已經定義好的異常,咱們再去用AssertionError觸發,這是徹底多餘的。例如:
def f(obj,index): assert index > len(obj), "IndexError" return obj[index]
該函數用來收集正在處理的異常的信息。
它返回一個包含3個值的元組(type, value, traceback)
,它們是當前正在處理的異常的信息。若是沒有正在處理的異常,則返回3個None組成的元組。
其中:
看一個示例便可知道。
class General(Exception):pass def raise0(): x = General() raise x try: raise0() except Exception: import sys print(sys.exc_info())
執行結果:
(<class '__main__.General'>, General(), <traceback object at 0x0388F2B0>)
結果很明顯,第一個返回值是異常類General,第二個返回值是拋出的異常類的實例對象,第三個返回值是traceback對象。
實際上,當須要獲取當前處理的異常類時,還能夠經過異常對象的__class__
來獲取,由於異常對象能夠在except/as中賦值給變量:
class General(Exception):pass def raise0(): x = General() raise x try: raise0() except Exception as E: import sys print(sys.exc_info()[0]) print(E.__class__)
它們的的結果是徹底同樣的:
<class '__main__.General'> <class '__main__.General'>
何時要獲取異常類的信息?當except所監視的異常類比較大範圍,同時又想知道具體的異常類。好比,except:
或except Exception:
這兩種監視的異常範圍都很是大,前者會監視BaseException,也就是python的全部異常,後者監視的是Exception,也就是python的全部普通的異常。正由於監視範圍太大,致使不知道具體是拋出的是哪一個異常。
錯誤都是異常,但異常並不必定都是錯誤。
很常見的,文件結尾的EOF在各類語言中它都定義爲異常,是異常就能被觸發捕獲,但在邏輯上卻不認爲它是錯誤。
除此以外,還有操做系統的異常,好比sys.exit()引起的SystemeExit異常,ctrl+c引起的的中斷異常KeyboardInterrupt都屬於異常,但它們和普通的異常不同。並且python中的普通異常都繼承字Exception類,但SystemExit卻並不是它的子類,而是BaseException的子類。因此能經過空的except:
捕獲到它,卻不能經過except Exception:
來捕獲。
全部異常類都繼承自Exception,要編寫自定義的異常時,要麼直接繼承該類,要麼繼承該類的某個子類。
例如,下面定義三個異常類,General類繼承Exception,另外兩個繼承General類,表示這兩個是特定的、更具體的異常類。
class General(Exception):pass class Specific1(General): pass class Specific2(General): pass def raise0(): x = General() raise x def raise1(): x = Specific1() raise x def raise2(): x = Specific2() raise x
測試下:
for func in (raise0, raise1, raise2): try: func() except General as E: import sys print("caught: ", E.__class__)
執行結果:
caught: <class '__main__.General'> caught: <class '__main__.Specific1'> caught: <class '__main__.Specific2'>
前面說過,except監視父類異常的時候,也會捕獲該類的子類異常。正如這裏監視的是Gereral類,但觸發了Specific子類異常也會被捕獲。
這是很是常見的陷阱。有兩種異常嵌套的方式:try的嵌套;代碼塊的異常嵌套(好比函數嵌套)。不管是哪一種嵌套模式,異常都只在最近(或者說是最內層)的代碼塊中被處理,可是finally塊是全部try都會執行的。
第一種try的嵌套模式:
try: try: (1) except xxx: (2) finally: (3) except yyy: ... finally: (4)
若是在(1)處拋出了異常,不管yyy是否匹配這個異常,只要xxx能匹配這個異常,就會執行(2)。但(3)、(4)這兩個finally都會執行。
第二種代碼塊嵌套,常見的是函數調用的嵌套,這種狀況可能會比較隱式。例如:
def action2(): print(1 + []) def action1(): try: action2() except TypeError: print('inner try') try: action1() except TypeError: print('outer try')
執行結果:
inner try
上面的action2()會拋出一個TypeError的異常。在action1()中用了try包圍action2()的調用,因而action2()的異常彙報給action1()層,而後被捕獲。
可是在最外面,使用try包圍action1()的調用,看上去異常也會被捕獲,但實際上並不會,由於在action2()中就已經經過except處理好了異常,而處理過的異常將再也不是異常,不會再觸發外層的異常,因此上面不會輸出"outer try"。
在考慮異常捕獲的時候,須要注意幾點:
第三點很容易理解,有些異常會致使程序沒法進行後續的運行,改中斷仍是得中斷。
對於第一點,可能經常使用的大範圍異常監視就是下面兩種方式:
except: except Exception:
這兩種方式監視的範圍都太大了,好比有些不想處理的異常也會被它們監視到。更糟糕的多是本該彙報給上層的異常,結果卻被這種大範圍捕獲了。例如:
def func(): try: ... except: ... try: func() except IndexErro: ...
原本是想在外層的try中明確捕獲func觸發的IndexError異常的,可是func()內卻使用了空的except:
,使得異常直接在這裏被處理,外層的try永遠也捕獲不到任何該函數的異常。
關於第二點,不該該監視範圍過小的異常。範圍小,意味着監視的異常太過具體,太過細緻,這種監視方式雖然精確,但卻不利於維護。例如E1異常類有2個子異常類E二、E3,在代碼中監視了E二、E3,但若是將來又添加了一個E1的子異常類E4,那麼又得去改代碼讓它監視E4。若是代碼是寫給本身用的倒無所謂,但若是像通用模塊同樣交給別人用的,這意味着讓別的模塊使用者也去改代碼。
在前面設計異常類的時候,老是使用pass跳過類代碼體。但卻仍然能使用這個類做爲異常類,由於它繼承了Exception,在Exception中有相關代碼能夠輸出異常信息。
前面說過,在構造異常類的時候能夠傳遞參數,它們會放進異常實例對象的args屬性中:
try: raise IndexError("something wrong") except Exception as E: print(E.args) try: assert False,"something wrong too" except Exception as E: print(E.args) I = IndexError('text') print(I.args)
對於用戶自定義的類,也同樣如此:
class MyError(Exception):pass try: raise MyError('something wrong') except MyError as E: print(E.args)
不只如此,雖然異常實例對象是一個對象,但若是直接輸出實例對象,那麼獲得的結果將是給定的異常信息,只不過它不在元組中。
I = IndexError("index wrong") print(I) # 輸出"index wrong"
很容易想到,這是由於Exception類中重寫了__str__
或者__repr__
中的某一個或兩個都重寫了。
因而,自定義異常類的時候,也能夠重寫這兩個中的一個,從而能夠定製屬於本身的異常類的輸出信息。通常來講只重寫__str__
,由於Exception中也是重寫該類,且它的優先級高於__repr__
。
例以下面自定義的異常類。固然,這個示例的效果很是簡陋,但已足夠說明問題。
class MyError(Exception): def __str__(self): return 'output this message for something wrong' try: raise MyError("hahhaha") except MyError as E: print(E)
輸出結果:
output this message for something wrong
自定義異常類的時候,能夠重寫構造方法__init__()
,這樣raise異常的時候,能夠指定構造的數據。並且更進一步的,還能夠重寫__str__
來自定義異常輸出。
例如,格式化文件的程序中定義一個異常類,用來提示解析到哪一個文件的哪一行出錯。
class MyError(Exception): def __init__(self,line,file): self.line = line self.file = file def __str__(self): return "format failed: %s at %s" % (self.file, self.line) def format(): ... raise MyError(42, "a.json") ... try: format() except MyError as E: print(E)
異常類既然是類,說明它能夠像普通類同樣拿來應用。好比添加其它類方法。
例如,能夠將異常信息寫入到文件中。只需提供一個向文件寫入的方法,並在except的語句塊中調用這個方法便可。
class MyError(Exception): def __init__(self, line, file): self.line = line self.file = file def logerr(self): with open('Error.log', 'a') as logfile: print(self, file=logfile) def __str__(self): return "format failed: %s at %s" % (self.file, self.line) def format(): raise MyError(42, "a.json") try: format() except MyError as E: E.logerr()