在程序運行過程中,總會遇到各種各樣的錯誤。
有的錯誤是程序編寫有問題造成的,比如本來應該輸出整數結果輸出了字符串,這種錯誤我們通常稱之為bug,bug是必須修復的。
有的錯誤是用戶輸入造成的,比如讓用戶輸入email地址,結果得到一個空字符串,這種錯誤可以通過檢查用戶輸入來做相應的處理。
還有一類錯誤是完全無法在程序運行過程中預測的,比如寫入文件的時候,磁盤滿了,寫不進去了,或者從網絡抓取數據,網絡突然斷掉了。這類錯誤也稱為異常,在程序中通常是必須處理的,否則,程序會因為各種問題終止並退出。
Python內置了一套異常處理機制,來幫助我們進行錯誤處理。
此外,我們也需要跟蹤程序的執行,查看變量的值是否正確,這個過程稱為調試。Python的pdb可以讓我們以單步方式執行代碼。
最后,編寫測試也很重要。有了良好的測試,就可以在程序修改后反復運行,確保程序輸出符合我們編寫的測試。
一、錯誤處理
1、語法錯誤
python的語法錯誤或者稱之為解析錯:SyntaxError: invalid syntax
2、異常
python程序的語法是正確的,但在運行期間檢測到的錯誤稱為異常,大多數的異常都不會被程序處理,都以錯誤信息的形式展現出來;常見的類型有: ZeroDivisionError,NameError 和 TypeError。
1 異常處理: 2 AttributeError 試圖訪問一個對象沒有的樹形,比如foo.x,但是foo沒有屬性x 3 IOError 輸入/輸出異常;基本上是無法打開文件 4 ImportError 無法引入模塊或包;基本上是路徑問題或名稱錯誤 5 IndentationError 語法錯誤(的子類) ;代碼沒有正確對齊 6 IndexError 下標索引超出序列邊界,比如當x只有三個元素,卻試圖訪問x[5] 7 KeyError 試圖訪問字典里不存在的鍵 8 KeyboardInterrupt Ctrl+C被按下 9 NameError 使用一個還未被賦予對象的變量 10 SyntaxError Python代碼非法,代碼不能編譯(個人認為這是語法錯誤,寫錯了) 11 TypeError 傳入對象類型與要求的不符合 12 UnboundLocalError 試圖訪問一個還未被設置的局部變量,基本上是由於另有一個同名的全局變量, 13 導致你以為正在訪問它 14 ValueError 傳入一個調用者不期望的值,即使值的類型是正確的
3、異常處理
高級語言通常都內置了一套try...except...finally...的錯誤處理機制,python也不例外。
異常處理的三種方式:
try: print(a) except: print('Error') #try...except... try: print(a) except TypeError as e: print(e) except NameError as e: print(e) #try...except NAME_ERROR as E try: print(a) except NameError as e: print(e) except: print('Error') finally: print('hello') #finally下的是,不管程序是否錯誤都執行的代碼塊
try: # 主代碼塊 pass except KeyError,e: # 異常時,執行該塊 pass else: # 主代碼塊執行完,執行該塊 pass finally: # 無論異常與否,最終執行該塊 pass
try語句按照如下方式工作;
- 首先,執行try子句(在關鍵字try和關鍵字except之間的語句)
- 如果沒有異常發生,忽略except子句,try子句執行后結束。
- 如果在執行try子句的過程中發生了異常,那么try子句余下的部分將被忽略。如果異常的類型和 except 之后的名稱相符,那么對應的except子句將被執行。最后執行 try 語句之后的代碼。
- 如果一個異常沒有與任何的except匹配,那么這個異常將會傳遞給上層的try中。
一個 try 語句可能包含多個except子句,分別來處理不同的特定的異常。最多只有一個分支會被執行。
處理程序將只針對對應的try子句中的異常進行處理,而不是其他的 try 的處理程序中的異常。
一個except子句可以同時處理多個異常,這些異常將被放在一個括號里成為一個元組,例如:
except (RuntimeError, TypeError, NameError): pass
最后一個except子句可以忽略異常的名稱,它將被當作通配符使用。
try except 語句還有一個可選的else子句,如果使用這個子句,那么必須放在所有的except子句之后。這個子句將在try子句沒有發生任何異常的時候執行。
try: a = 1 print(a) except NameError as e: print(e) except: print('Error') else: print('hello') #else里的代碼塊是在try內的代碼沒有發生錯誤的時候才執行 finally: print('world') #finally里的代碼塊是不管try里的代碼是否正確都執行
使用 else 子句比把所有的語句都放在 try 子句里面要好,這樣可以避免一些意想不到的、而except又沒有捕獲的異常。
4、記錄錯誤
如果不捕獲錯誤,自然可以讓Python解釋器來打印出錯誤堆棧,但程序也被結束了。既然我們能捕獲錯誤,就可以把錯誤堆棧打印出來,然后分析錯誤原因,同時,讓程序繼續執行下去。
Python內置的logging
模塊可以非常容易地記錄錯誤信息:
import logging def foo(s): return 10 / int(s) def bar(s): return foo(s) * 2 def main(): try: res = bar('0') print(res) except Exception as e: # print(e) logging.exception(e) #記錄錯誤信息 main() print('END')
同樣是出錯,但程序打印完錯誤信息后會繼續執行,並正常退出。
5、拋出錯誤
因為錯誤是class,捕獲一個錯誤就是捕獲到該class的一個實例。因此,錯誤並不是憑空產生的,而是有意創建並拋出的。Python的內置函數會拋出很多類型的錯誤,我們自己編寫的函數也可以拋出錯誤。只有在必要的時候才定義我們自己的錯誤類型。
捕獲錯誤的目的只是記錄一下,便於后續追蹤。可以通過raise語句來實現:
try: print(a) except NameError as e: print(e) raise
raise語句如果不帶參數,就會把當前錯誤原樣拋出。此外,在except中raise一個Error還可以把一種類型的錯誤轉化成另一種類型,只要是合理的轉換邏輯就可以。
二、調試
程序能一次寫完並正常運行的概率很小,基本不超過1%。總會有各種各樣的bug需要修正。有的bug很簡單,看看錯誤信息就知道,有的bug很復雜,我們需要知道出錯時,哪些變量的值是正確的,哪些變量的值是錯誤的,因此,需要一整套調試程序的手段來修復bug。
1、簡單直接、粗暴有效的就是用print()把可能有問題的變量打印出來看看
def foo(s): n = int(s) print('>>n = %d' % n) return 10 / n def main(): foo('0') main()
用print()最大的壞處是將來還得刪掉它,想想程序里到處都是print(),運行結果中會包含很多垃圾信息。
2、斷言
凡是用print()來輔助查看的地方,都可以用斷言(assert)來替代:
def foo(s): n = int(s) assert n != 0,'s is zero' return 10 / n def main(): foo('0') main()
assert的意思是,表達式 n != 0應該是True,否則,根據程序運行的邏輯,后面的代碼肯定會出錯;如果斷言失敗,assert語句本身就會拋出AssertionError
啟動python解釋器時可以用-O參數來關閉assert,注意,“-O”時英文字母大寫的O;關閉后,我們可以把所有的assert語句當成pass來看。
3、logging
把print()替換為logging是第三種方式,和assert比,logging不會拋出錯誤,而且可以輸出到文件:
import logging s = '0' n = int(s) logging.info('n = %d' %n) print(10 / n)
logging允許你指定記錄信息的級別,有debug,info,warning,error等幾個級別,當我們指定level=INFO時,logging.debug就不起作用了。同理,指定level=WARNING后,debug和info就不起作用了。這樣一來,我們就可以放心地輸出不同級別的信息,也不用刪除,最后統一控制輸出哪個級別的信息。
logging的另一個好處是通過簡單的配置,一條語句可以同時輸出到不同的地方,比如console和文件。
4、pdb
啟動python的調試器pdb,讓程序以單步方式運行,可以隨時查看運行狀態。(-m pdb)
輸入命令1來查看代碼
輸入命令n可以單步執行代碼
輸入命令p+變量名來查看變量
輸入命令q結束調試,退出程序
5、pdb.set_trace()
這個方法也是用pdb,但是不需要單步執行,我們只需要import pdb,然后,在可能出錯的地方放一個pdb.set_trace(),就可以設置一個斷點
import pdb s = '0' n = int(s) pdb.set_trace() #程序運行到這里會暫停進入調試模式 print(10 / n)
運行代碼,程序會自動在pdb.set_trace()暫停並進入pdb調試環境,可以用命令p查看變量,或者用命令c繼續運行
6、IDE [集成開發環境(integrated development environment)]
如果要比較爽地設置斷點、單步執行,就需要一個支持調試功能的IDE。目前比較好的Python IDE有:
Visual Studio Code:https://code.visualstudio.com/,需要安裝Python插件。
PyCharm:http://www.jetbrains.com/pycharm/
另外,Eclipse加上pydev插件也可以調試Python程序。