Python學習之錯誤調試和測試

Python學習目錄python

  1. 在Mac下使用Python3
  2. Python學習之數據類型
  3. Python學習之函數
  4. Python學習之高級特性
  5. Python學習之函數式編程
  6. Python學習之模塊
  7. Python學習之面向對象編程
  8. Python學習之面向對象高級編程
  9. Python學習之錯誤調試和測試
  10. Python學習之IO編程
  11. Python學習之進程和線程
  12. Python學習之正則
  13. Python學習之經常使用模塊
  14. Python學習之網絡編程

在程序運行過程當中,總會遇到各類各樣的錯誤,Python內置了一套異常處理機制,來幫助咱們進行錯誤處理。編程

debug

錯誤處理

在程序運行的過程當中,若是發生了錯誤,能夠事先約定返回一個錯誤代碼,這樣,就能夠知道是否有錯,以及出錯的緣由。在操做系統提供的調用中,返回錯誤碼很是常見。好比打開文件的函數open(),成功時返回文件描述符(就是一個整數),出錯時返回-1網絡

用錯誤碼來表示是否出錯十分不便,由於函數自己應該返回的正常結果和錯誤碼混在一塊兒,形成調用者必須用大量的代碼來判斷是否出錯,因此高級語言一般都內置了一套try...except...finally...的錯誤處理機制,Python也不例外。eclipse

try

try:
    print('try...')
    r = 10 / 0
    print('result:', r)
except ZeroDivisionError as e:
    print('except:', e)
finally:
    print('finally...')
print('END')
複製代碼

當咱們認爲某些代碼可能會出錯時,就能夠用try來運行這段代碼,若是執行出錯,則後續代碼不會繼續執行,而是直接跳轉至錯誤處理代碼,即except語句塊,執行完except後,若是有finally語句塊,則執行finally語句塊,至此,執行完畢。函數式編程

Python的錯誤其實也是class,全部的錯誤類型都繼承自BaseException,因此在使用except時須要注意的是,它不但捕獲該類型的錯誤,還把其子類也「一網打盡」。函數

調用棧

若是錯誤沒有被捕獲,它就會一直往上拋,最後被Python解釋器捕獲,打印一個錯誤信息,而後程序退出。來看看err.py工具

# err.py:
def foo(s):
    return 10 / int(s)

def bar(s):
    return foo(s) * 2

def main():
    bar('0')

main()
複製代碼

執行,結果以下:post

$ python3 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: division by zero
複製代碼

咱們從上往下能夠看到整個錯誤的調用函數鏈。單元測試

記錄錯誤

若是不捕獲錯誤,天然可讓Python解釋器來打印出錯誤堆棧,但程序也被結束了。既然咱們能捕獲錯誤,就能夠把錯誤堆棧打印出來,而後分析錯誤緣由,同時,讓程序繼續執行下去。學習

Python內置的logging模塊能夠很是容易地記錄錯誤信息:

# err_logging.py

import logging

def foo(s):
    return 10 / int(s)

def bar(s):
    return foo(s) * 2

def main():
    try:
        bar('0')
    except Exception as e:
        logging.exception(e)

main()
print('END')
複製代碼

執行上述模塊:

$ python3 err_logging.py
ERROR:root:division by zero
Traceback (most recent call last):
  File "err_logging.py", line 13, in main
    bar('0')
  File "err_logging.py", line 9, in bar
    return foo(s) * 2
  File "err_logging.py", line 6, in foo
    return 10 / int(s)
ZeroDivisionError: division by zero
END
複製代碼

拋出錯誤

由於錯誤是class,捕獲一個錯誤就是捕獲到該class的一個實例。所以,錯誤並非憑空產生的,而是有意建立並拋出的。Python的內置函數會拋出不少類型的錯誤,咱們本身編寫的函數也能夠拋出錯誤。

若是要拋出錯誤,首先根據須要,能夠定義一個錯誤的class,選擇好繼承關係,而後,用raise語句拋出一個錯誤的實例:

# err_raise.py
class FooError(ValueError):
    pass

def foo(s):
    n = int(s)
    if n==0:
        raise FooError('invalid value: %s' % s)
    return 10 / n

foo('0')
複製代碼

執行,能夠最後跟蹤到咱們本身定義的錯誤:

$ python3 err_raise.py 
Traceback (most recent call last):
  File "err_throw.py", line 11, in <module>
    foo('0')
  File "err_throw.py", line 8, in foo
    raise FooError('invalid value: %s' % s)
__main__.FooError: invalid value: 0
複製代碼

最後,咱們來看另外一種錯誤處理的方式:

# err_reraise.py

def foo(s):
    n = int(s)
    if n==0:
        raise ValueError('invalid value: %s' % s)
    return 10 / n

def bar():
    try:
        foo('0')
    except ValueError as e:
        print('ValueError!')
        raise

bar()
複製代碼

bar()函數中,咱們明明已經捕獲了錯誤,可是,打印一個ValueError!後,又把錯誤經過raise語句拋出去了,這不有病麼?

其實這種錯誤處理方式不但沒病,並且至關常見。捕獲錯誤目的只是記錄一下,便於後續追蹤。可是,因爲當前函數不知道應該怎麼處理該錯誤,因此,最恰當的方式是繼續往上拋,讓頂層調用者去處理。比如一個員工處理不了一個問題時,就把問題拋給他的老闆,若是他的老闆也處理不了,就一直往上拋,最終會拋給CEO去處理。

raise語句若是不帶參數,就會把當前錯誤原樣拋出。此外,在exceptraise一個Error,還能夠把一種類型的錯誤轉化成另外一種類型:

try:
    10 / 0
except ZeroDivisionError:
    raise ValueError('input error!')
複製代碼

調試

程序能一次寫完並正常運行的機率很小,基本不超過1%。總會有各類各樣的bug須要修正。有的bug很簡單,看看錯誤信息就知道,有的bug很複雜,咱們須要知道出錯時,哪些變量的值是正確的,哪些變量的值是錯誤的,所以,須要一整套調試程序的手段來修復bug。

print()

print()把可能有問題的變量打印出來看看。

斷言

def foo(s):
    n = int(s)
    assert n != 0, 'n is zero!'
    return 10 / n

def main():
    foo('0')
複製代碼

assert的意思是,表達式n != 0應該是True,不然,根據程序運行的邏輯,後面的代碼確定會出錯。

若是斷言失敗,assert語句自己就會拋出AssertionError

logging

logging容許你指定記錄信息的級別,有debuginfowarningerror等幾個級別,當咱們指定level=INFO時,logging.debug就不起做用了。同理,指定level=WARNING後,debuginfo就不起做用了。這樣一來,你能夠放心地輸出不一樣級別的信息,也不用刪除,最後統一控制輸出哪一個級別的信息。

logging的另外一個好處是經過簡單的配置,一條語句能夠同時輸出到不一樣的地方,好比console和文件。

import logging
logging.basicConfig(level=logging.INFO)

s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)
複製代碼

pdb

Python的調試器pdb,讓程序以單步方式運行,能夠隨時查看運行狀態。咱們先準備好程序:

# err.py
s = '0'
n = int(s)
print(10 / n)
複製代碼

而後啓動:

$ python -m pdb err.py
> /Users/michael/Github/learn-python3/samples/debug/err.py(2)<module>()
-> s = '0'
複製代碼

以參數-m pdb啓動後,pdb定位到下一步要執行的代碼-> s = '0'。輸入命令l來查看代碼:

(Pdb) l
  1     # err.py
  2  -> s = '0'
  3     n = int(s)
  4     print(10 / n)
複製代碼

輸入命令n能夠單步執行代碼。

pdb.set_trace()

這個方法也是用pdb,可是不須要單步執行,咱們只須要import pdb,而後,在可能出錯的地方放一個pdb.set_trace(),就能夠設置一個斷點:

# err.py
import pdb

s = '0'
n = int(s)
pdb.set_trace() # 運行到這裏會自動暫停
print(10 / n)
複製代碼

運行代碼,程序會自動在pdb.set_trace()暫停並進入pdb調試環境,能夠用命令p查看變量,或者用命令c繼續運行。

IDE

目前比較好的Python IDE有:

isual Studio Code:code.visualstudio.com/,須要安裝Python插件。

PyCharm:www.jetbrains.com/pycharm/

另外,Eclipse加上pydev插件也能夠調試Python程序。

單元測試

單元測試是用來對一個模塊、一個函數或者一個類來進行正確性檢驗的測試工做。

文檔測試

若是你常常閱讀Python的官方文檔,能夠看到不少文檔都有示例代碼。

能夠把這些示例代碼在Python的交互式環境下輸入並執行,結果與文檔中的示例代碼顯示的一致。

這些代碼與其餘說明能夠寫在註釋中,而後,由一些工具來自動生成文檔。

下一篇:Python學習之IO編程

相關文章
相關標籤/搜索