程序運行時,會遇到各類各樣的錯誤。html
編寫錯誤叫作bug,而另外一類因爲運行過程當中沒法預測的,好比寫文件時,磁盤滿了,寫不進去;或者從網絡抓取數據,網絡忽然掉了。這些錯誤稱爲異常,程序中須要對異常進行處理,使得程序可以運行下去。python
Python中,程序運行錯誤時,若是錯誤沒有捕獲,它會一直往上拋,最後被Python解釋器捕獲,打印一個錯誤。網絡
# err.py: def foo(s): return 10 / int(s) def bar(s): return foo(s) * 2 def main(): bar('0') main()
$ python err.py Traceback (most recent call last): File "err.py", line 11, in <module> main() File "err.py", line 9, in main bar('0') File "err.py", line 6, in bar return foo(s) * 2 File "err.py", line 3, in foo return 10 / int(s) ZeroDivisionError: integer division or modulo by zero
從上到下,錯誤會一層層的反饋,直到顯示最終出錯的地方。函數
try...except...finally...:經常使用這種方法來檢查錯誤並捕捉到,同時進行相應的處理。單元測試
try: print 'try...' r = 10 / 0 print 'result:', r except ZeroDivisionError, e: print 'except:', e finally: print 'finally...' print 'END'
try... except: integer division or modulo by zero finally... END
注意到,錯誤類型有不少種,它們其實都是從BaseException類派生出來的,常見的錯誤類型和繼承關係有:https://docs.python.org/2/library/exceptions.html#exception-hierarchy學習
程序運行一次就成功的機率很小,基本上不超過1%。通常有以下的調試方法:測試
第一種,直接在可能出錯的地方print出來,可是後期會一個個刪掉。spa
第二種,使用斷言來代替。命令行
# err.py def foo(s): n = int(s) assert n != 0, 'n is zero!' return 10 / n def main(): foo('0')
斷言中,若是「n != 0」是錯的,就拋出AssertionError,並顯示後面的字符串。debug
第三種,使用logging。
logging有debug,info,warning,error等幾個級別,從前到後優先級依次提升,即若是,指定level=WARNING後,debug和info就不起做用了。這樣同樣,就能夠輸出不一樣級別的信息,也不用刪除,最後統一控制輸出哪一個級別的信息。
logging的另外一個好處是經過簡單的配置,一條語句能夠同時輸出到不一樣的地方,,好比console和文件。
# err.py import logging
logging.basicConfig(level=logging.INFO) s = '0' n = int(s) logging.info('n = %d' % n) print 10 / n
$ python err.py INFO:root:n = 0 Traceback (most recent call last): File "err.py", line 8, in <module> print 10 / n ZeroDivisionError: integer division or modulo by zero
第四種調試方式,就是調試器pdb,讓程序以單步方式運行,能夠隨時查看運行狀態。
# err.py s = '0' n = int(s) print 10 / n
而後,以參數-m pdb啓動,單步運行
$ python -m pdb err.py > /Users/michael/Github/sicp/err.py(2)<module>() -> s = '0'
(Pdb) l 1 # err.py 2 -> s = '0' 3 n = int(s) 4 print 10 / n [EOF]
輸入n單步運行下一步
(Pdb) n > /Users/michael/Github/sicp/err.py(3)<module>() -> n = int(s) (Pdb) n > /Users/michael/Github/sicp/err.py(4)<module>() -> print 10 / n
輸入p 變量名來查看變量狀態。
(Pdb) p s '0' (Pdb) p n 0
輸入q結束運行
(Pdb) n ZeroDivisionError: 'integer division or modulo by zero' > /Users/michael/Github/sicp/err.py(4)<module>() -> print 10 / n (Pdb) q
另外在合適的地方,設置pdb.set_trace(),能夠做爲斷點。
# err.py import pdb s = '0' n = int(s) pdb.set_trace() # 運行到這裏會自動暫停 print 10 / n
$ python err.py > /Users/michael/Github/sicp/err.py(7)<module>() -> print 10 / n (Pdb) p n 0 (Pdb) c Traceback (most recent call last): File "err.py", line 7, in <module> print 10 / n ZeroDivisionError: integer division or modulo by zero
到達斷點時,進入pdb調試器。
最後,還有方便的IDE調試器。
單元測試,顧名思義,就是對一個部分測試,能夠是一個模塊、一個函數或者一個類。它的目的是保證該單元可以實現原先規劃的功能,爲以後的總體調試作準備。
例如,現有模塊mydict.py,對它的要求是實現以下功能:
>>> d = Dict(a=1, b=2) >>> d['a'] 1 >>> d.a 1
mydict.py代碼以下:
class Dict(dict): def __init__(self, **kw): super(Dict, self).__init__(**kw) def __getattr__(self, key): try: return self[key] except KeyError: raise AttributeError(r"'Dict' object has no attribute '%s'" % key) def __setattr__(self, key, value): self[key] = value
編寫的單元測試,須要引入unittest模塊,編寫的mydict_test.py以下:
import unittest from mydict import Dict class TestDict(unittest.TestCase): def test_init(self): # 測試初始化功能 d = Dict(a=1, b='test') self.assertEquals(d.a, 1) self.assertEquals(d.b, 'test') self.assertTrue(isinstance(d, dict)) def test_key(self): # 測試key的功能 d = Dict() d['key'] = 'value' self.assertEquals(d.key, 'value') def test_attr(self): # 測試屬性功能 d = Dict() d.key = 'value' self.assertTrue('key' in d) self.assertEquals(d['key'], 'value') def test_keyerror(self): # 測試key錯誤的功能 d = Dict() with self.assertRaises(KeyError): value = d['empty'] def test_attrerror(self): # 測試屬性錯誤的功能 d = Dict() with self.assertRaises(AttributeError): value = d.empty
編寫的單元測試類,從unittest.TestCase繼承。其中,只有以test開頭的方法是測試方法。
運行單元測試時,能夠在測試文件中加入:
if __name__ == '__main__': unittest.main()
而後run。
另外一種,在命令行中輸入命令:
$ python -m unittest mydict_test ..... ---------------------------------------------------------------------- Ran 5 tests in 0.000s OK
第二種方法,能夠一次運行多個測試文件,比較方便。
setUp與tearDown:在每一個測試方法先後分別被執行,避免在測試代碼中重複加入代碼。
最後,單元測試要考慮到異常,代碼不能過於複雜,以避免自己就有bug。
Python中能夠提供實例文檔,在文件中編寫特定格式的註釋,調用doctest判斷程序是否會像註釋中那樣的運行。
class Dict(dict): ''' Simple dict but also support access as x.y style. >>> d1 = Dict() >>> d1['x'] = 100 >>> d1.x 100 >>> d1.y = 200 >>> d1['y'] 200 >>> d2 = Dict(a=1, b=2, c='3') >>> d2.c '3' >>> d2['empty'] Traceback (most recent call last): ... KeyError: 'empty' >>> d2.empty Traceback (most recent call last): ... AttributeError: 'Dict' object has no attribute 'empty' ''' def __init__(self, **kw): super(Dict, self).__init__(**kw) def __getattr__(self, key): try: return self[key] except KeyError: raise AttributeError(r"'Dict' object has no attribute '%s'" % key) def __setattr__(self, key, value): self[key] = value if __name__=='__main__': import doctest doctest.testmod()
而後run。若是什麼都沒輸出,就說明編寫的doctest運行都是正確的。
注:本文爲學習廖雪峯Python入門整理後的筆記