Day-10: 錯誤、調試和測試

  程序運行時,會遇到各類各樣的錯誤。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入門整理後的筆記

相關文章
相關標籤/搜索