在寫代碼的時候,常常會遇到異常。
python提供了兩個功能來處理程序在運行中出現的異常和錯誤,可使用該功能來調試python程序。 python
Exception 它能夠捕獲任意(絕大部分)異常。 AttributeError 試圖訪問一個對象沒有的樹形,好比foo.x,可是foo沒有屬性x IOError 輸入/輸出異常;基本上是沒法打開文件 ImportError 沒法引入模塊或包;基本上是路徑問題或名稱錯誤 IndentationError 語法錯誤(的子類),代碼沒有正確對齊 IndexError 下標索引超出序列邊界,好比當x只有三個元素,卻試圖訪問x[5] KeyError 試圖訪問字典裏不存在的鍵 KeyboardInterrupt Ctrl+C被按下 NameError 使用一個還未被賦予對象的變量 SyntaxError Python代碼非法,代碼不能編譯(我的認爲這是語法錯誤,寫錯了) TypeError 傳入對象類型與要求的不符合 UnboundLocalError 試圖訪問一個還未被設置的局部變量,基本上是因爲另有一個同名的全局變量,致使你覺得正在訪問它 ValueError 傳入一個調用者不指望的值,即便值的類型是正確的
異常便是一個事件,該事件會在程序執行過程當中發生,影響程序的正常執行。
通常狀況下,在Python沒法正常處理程序時就會發生一個異常,異常是Python對象,表示一
個錯誤。 數據庫
當Python腳本發生異常時咱們須要捕獲處理它,不然程序會終止執行。 express
#!/usr/bin/env python try: print "%d" % (5 / 0) except ZeroDivisionError: print "除數不能爲零" else: print "沒有報錯" print "這是異常以後的代碼" #若是沒有上面的異常處理,下面的代碼是不會執行的 for i in range(10): print i
try/except語句用來檢測try語句塊中的錯誤,從而讓except語句捕獲異常信息並處理。 若是你不想在異常發生時結束你的程序,只需在try裏捕獲它。 語法: try: <語句> #運行別的代碼 except <名字>: <語句> #若是在try部份引起了'name'異常 except <名字>,<數據>: <語句> #若是引起了'name'異常,得到附加的數據 else: <語句> #若是沒有異常發生
當開始一個try語句後,python就在當前程序的上下文中做標記,這樣當異常出現時就可
以回到這裏,try子句先執行,接下來會發生什麼依賴於執行時是否出現異常。 編程
1. 若是當try後的語句執行時發生異常,python就跳回到try並執行第一個匹配該異常的 except子句,異常處理完畢,控制流就經過整個try語句(除非在處理異常時又引起新的異 常)。 2. 若是在try後的語句裏發生了異常,卻沒有匹配的except子句,異常將被遞交到上層的 try,或者到程序的最上層(這樣將結束程序,並打印缺省的出錯信息)。 3. 若是在try子句執行時沒有發生異常,python將執行else語句後的語句(若是有else的 話),而後控制流經過整個try語句。
例安全
打開一個文件,在該文件中的寫入內容,且並未發生異常: try: fh = open("testfile", "w") fh.write("這是一個測試文件,用於測試異常!!") except IOError: print "Error: 沒有找到文件或讀取文件失敗" else: print "內容寫入文件成功" fh.close() 結果: # python test.py 內容寫入文件成功 # cat testfile # 查看寫入的內容 這是一個測試文件,用於測試異常!! 例 打開一個文件,在該文件中的內容寫入內容,但文件沒有寫入權限,發生了異常: try: fh = open("testfile", "w") fh.write("這是一個測試文件,用於測試異常!!") except IOError: print "Error: 沒有找到文件或讀取文件失敗" else: print "內容寫入文件成功" fh.close() 在執行代碼前爲了測試方便,先去掉 testfile 文件的寫權限 再執行以上代碼: $ python test.py #注意這裏用的是普通用戶 Error: 沒有找到文件或讀取文件失敗
你能夠不帶任何異常類型使用except,以下實例:app
try: 正常的操做 ...................... except: 發生異常,執行這塊代碼 ...................... else: 若是沒有異常執行這塊代碼
以上方式try-except語句捕獲全部發生的異常。但這不是一個很好的方式,咱們不能經過該程序
識別出具體的異常信息。由於它捕獲全部的異常。 運維
也可使用相同的except語句來處理多個異常信息: ide
try: 正常的操做 ................ except(Exception1[, Exception2[,...ExceptionN]]]): 發生以上多個異常中的一個,執行這塊代碼 ...................... else: 若是沒有異常執行這塊代碼
try-finally 語句不管是否發生異常都將執行最後的代碼。 函數
try: <語句> finally: <語句> #退出try時總會執行
例1:單元測試
try: fh = open("testfile", "w") fh.write("這是一個測試文件,用於測試異常!!") finally: print "Error: 沒有找到文件或讀取文件失敗"
例2:
import time try: f=file("文件.py") while True: line = f.read() if len(line)==0: break time.sleep(2) print line, finally: f.close() print "hello"
例3:
try: fh = open("testfile", "w") try: fh.write("這是一個測試文件,用於測試異常!!") finally: print "關閉文件" fh.close() except IOError: print "Error: 沒有找到文件或讀取文件失敗"
一個異常能夠帶上參數,可做爲輸出的異常信息參數。
你能夠經過except語句來捕獲異常的參數,以下所示:
try: 正常的操做 ...................... except ExceptionType, Argument: 你能夠在這輸出 Argument 的值...
變量接收的異常值一般包含在異常的語句中。在元組的表單中變量能夠接收一個或者多個
值。
元組一般包含錯誤字符串,錯誤數字,錯誤位置。
例
如下爲單個異常的實例:
#!/usr/bin/python def temp_convert(var): try: return int(var) except ValueError, Argument: print "參數沒 有包含數字\n", Argument # 調用函數 temp_convert("xyz") 以上程序執行結果以下: $ python test.py 參數沒有包含數字 invalid literal for int() with base 10: 'xyz'
可使用raise語句本身觸發異常
raise語法格式:
raise [Exception [, args [, traceback]]]
語句中Exception是異常的類型(例如,NameError)參數是一個異常參數值。該參數是可
選的,若是不提供,異常的參數是"None"。
最後一個參數是可選的(在實踐中不多使用),若是存在,是跟蹤異常對象。
例
一個異常能夠是一個字符串,類或對象。 Python的內核提供的異常,大多數都是實例化的
類,這是一個類的實例的參數。
定義一個異常:
def functionName( level ): if level < 1: raise Exception("Invalid level!", level) # 觸發異常後,後面的代碼就不會再執行
注意:爲了可以捕獲異常,"except"語句必須有用相同的異常來拋出類對象或者字符串。
例如咱們捕獲以上異常,"except"語句以下:
try: 正常邏輯 except "Invalid level!": 觸發自定義異常 else: 其他代碼
例
#!/usr/bin/python def mye( level ): if level < 1: raise Exception("Invalid level!", level) # 觸發異常後,後面的代碼就不會再執行 try: mye(0) # 觸發異常 except "Invalid level!": print 1 else: print 2 輸出結果: $ python test.py Traceback (most recent call last): File "test.py", line 11, in <module> mye(0) File "test.py", line 7, in mye raise Exception("Invalid level!", level) Exception: ('Invalid level!', 0)
經過建立一個新的異常類,程序能夠命名它們本身的異常。異常應該是典型的繼承自
Exception類,經過直接或間接的方式。
如下爲與RuntimeError相關的實例,實例中建立了一個類,基類爲RuntimeError,用於在
異常觸發時輸出更多的信息。
在try語句塊中,用戶自定義的異常後執行except塊語句,變量 e 是用於建立Networkerror
類的實例。
class Networkerror(RuntimeError): def __init__(self, arg): self.args = arg 在你定義以上類後,你能夠觸發該異常,以下所示: try: raise Networkerror("Bad hostname") except Networkerror,e: print e.args
在python的異常中,有一個萬能異常:Exception,它能夠捕獲任意異常。
例:
#cat aa.py s1 = 'hello' try: int(s1) except Exception,e: print e 執行結果: #python aa.py invalid literal for int() with base 10: 'hello'
既然有這個萬能異常,其餘異常是否是就能夠忽略了?固然不是,對於特殊處理或提醒的異常須要先定義,最後定義Exception來確保程序正常運行。
例:
s1 = 'hello' try: int(s1) except KeyError,e: print '鍵錯誤' except IndexError,e: print '索引錯誤' except Exception, e: print '錯誤'
======================================
使用assert斷言是學習python一個很是好的習慣,assert斷言句語格式及用法很簡單。在沒完善一個程序以前,咱們不知道程序在哪裏會出錯,與其讓它在運行最崩潰,不如在出現錯誤條件時就崩潰,這時候就須要assert斷言的幫助。
assert斷言是聲明其布爾值必須爲真的斷定,若是發生異常就說明表達示爲假。能夠理解assert斷言語句爲raise-if-not,用來測試表示式,其返回值爲假,就會觸發異常。
assert斷言語句的語法格式
assert expression
一些assert用法的語句供參考:
assert 1==1 assert 2+2==2*2 assert len(['my boy',12])<10 assert range(4)==[0,1,2,3]
assert的異常參數,其實就是在斷言表達式後添加字符串信息,用來解釋斷言並更好的知道是哪裏出了問題。格式以下:
assert expression [, arguments]
Python的assert是用來檢查一個條件,若是它爲真,就不作任何事。若是它爲假,則會拋出AssertError而且包含錯誤信息。例如:
py> x = 23 py> assert x > 0, "x is not zero or negative" py> assert x%2 == 0, "x is not an even number" Traceback (most recent call last): File "", line 1, in .... AssertionError: x is not an even number
不少人用assert做爲一個很快和容易的方法來在參數錯誤的時候拋出異常。但這樣作是錯的,很是錯誤,有兩個緣由。首先AssertError不是在測試參數時應該拋出的錯誤。你不該該像這樣寫代碼:
if not isinstance(x, int): raise AssertionError("not an int")
你應該拋出TypeError的錯誤,assert會拋出錯誤的異常。
可是,更危險的是,有一個關於assert的困擾:它能夠被編譯好而後歷來不執行,若是你用 –O 或 –oo 選項運行Python,結果不保證assert表達式會運行到。當適當的使用assert時,這是將來,可是當assert不恰當的使用時,它會讓代碼用-O執行時出錯。
那何時應該使用assert?沒有特定的規則,斷言應該用於:
防護型的編程 運行時檢查程序邏輯 檢查約定 程序常量 檢查文檔
(在測試代碼的時候使用斷言也是可接受的,是一種很方便的單元測試方法,你接受這些測試在用-O標誌運行時不會作任何事。我有時在代碼裏使用assert False來標記沒有寫完的代碼分支,我但願這些代碼運行失敗。儘管拋出NotImplementedError可能會更好。)
關於斷言的意見有不少,由於它能確保代碼的正確性。若是你肯定代碼是正確的,那麼就沒有用斷言的必要了,由於他們歷來不會運行失敗,你能夠直接移除這些斷言。若是你肯定檢查會失敗,那麼若是你不用斷言,代碼就會經過編譯並忽略你的檢查。
在以上兩種狀況下會頗有意思,當你比較確定代碼可是不是絕對確定時。可能你會錯過一些很是古怪的狀況。在這個狀況下,額外的運行時檢查能幫你確保任何錯誤都會盡早地被捕捉到。
另外一個好的使用斷言的方式是檢查程序的不變量。一個不變量是一些你須要依賴它爲真的狀況,除非一個bug致使它爲假。若是有bug,最好可以儘早發現,因此咱們爲它進行一個測試,可是又不想減慢代碼運行速度。因此就用斷言,由於它能在開發時打開,在產品階段關閉。
一個非變量的例子多是,若是你的函數但願在它開始時有數據庫的鏈接,而且承諾在它返回的時候仍然保持鏈接,這就是函數的不變量:
def some_function(arg): assert not DB.closed() ... # code goes here assert not DB.closed() return result
斷言自己就是很好的註釋,賽過你直接寫註釋:
# when we reach here, we know that n > 2 你能夠經過添加斷言來確保它: assert n > 2
斷言也是一種防護型編程。你不是讓你的代碼防護如今的錯誤,而是防止在代碼修改後引起的錯誤。理想狀況下,單元測試能夠完成這樣的工做,但是須要面對的現實是,它們一般是沒有完成的。人們可能在提交代碼前會忘了運行測試代碼。有一個內部檢查是另外一個阻擋錯誤的防線,尤爲是那些不明顯的錯誤,卻致使了代碼出問題而且返回錯誤的結果。
加入你有一些if…elif 的語句塊,你知道在這以前一些須要有一些值:
# target is expected to be one of x, y, or z, and nothing else. if target == x: run_x_code() elif target == y: run_y_code() else: run_z_code()
假設代碼如今是徹底正確的。但它會一直是正確的嗎?依賴的修改,代碼的修改。若是依賴修改爲 target = w 會發生什麼,會關係到run_w_code函數嗎?若是咱們改變了代碼,但沒有修改這裏的代碼,可能會致使錯誤的調用 run_z_code 函數並引起錯誤。用防護型的方法來寫代碼會很好,它能讓代碼運行正確,或者立馬執行錯誤,即便你在將來對它進行了修改。
在代碼開頭的註釋很好的一步,可是人們常常懶得讀或者更新註釋。一旦發生這種狀況,註釋會變得沒用。但有了斷言,我能夠同時對代碼塊的假設書寫文檔,而且在它們違反的時候觸發一個乾淨的錯誤
assert target in (x, y, z) if target == x: run_x_code() elif target == y: run_y_code() else: assert target == z run_z_code()
這樣,斷言是一種防護型編程,同時也是一種文檔。我想到一個更好的方案:
if target == x: run_x_code() elif target == y: run_y_code() elif target == z: run_z_code() else: # This can never happen. But just in case it does... raise RuntimeError("an unexpected error occurred")
按約定進行設計是斷言的另外一個好的用途。咱們想象函數與調用者之間有個約定,好比下面的:
「若是你傳給我一個非空字符串,我保證傳會字符串的第一個字母並將其大寫。」
若是約定被函數或調用這破壞,代碼就會出問題。咱們說函數有一些前置條件和後置條件,因此函數就會這麼寫:
def first_upper(astring): assert isinstance(astring, str) and len(astring) > 0 result = astring[0].upper() assert isinstance(result, str) and len(result) == 1 assert result == result.upper() return result
按約定設計的目標是爲了正確的編程,前置條件和後置條件是須要保持的。這是斷言的典型應用場景,由於一旦咱們發佈了沒有問題的代碼到產品中,程序會是正確的,而且咱們能安全的移除檢查。
建議不要用斷言的場景: