robot
最新文章(10)
Mqskit 和其它相關工具
CPython 的 GC 二、三事
寫 Mecurial Extension 是件快樂的事!
Mozilla 台灣辨公室徵人啟事
關於 Apple 的兩項專利
core dump 之前的 frame
怎麼發出 beep 聲?
先承認你要找的是奴才吧!
程式碼要清的多乾淨?
FreeBSD 的 Thread-Local Storage 實作
首頁
新編
最新留言
Entries RSS
重要關鍵字(10)
coding (122)
Python (93)
FreeBSD (71)
WEB (61)
URL (48)
hardware (46)
javascript (36)
Linux (34)
blog (30)
C++ (16)
所有關鍵字
新增 URL
Python 錯誤處理
by thinker
2 Columns
關鍵字:
Python
coding
程式執行時,若發生錯誤該怎麼辨?許多時侯,我們都假設程式不應該出錯。但,出錯是在所難免,往往有當初不預期的狀況出現。因此,保留適當的訊息變的非常重要。這些訊息,是 programmer 解決問題的重要依據。尤其網路的 service ,通常在遠端的機器上執行,若不保留足夠的資訊,往往無法重建錯誤發生當時的狀況。因此, error message 的 log 就變的非常重要。 $Python$ 對錯誤的反應,往往是產生 exception 。在 exception 發生時,會將 exception 的內容輸出到 stderr 。似乎只需將這些訊息保留下來, programmer 就能據以追蹤問題發生的原因。但事實上,以預設的錯誤訊息進行除錯,常常很沒有效率。 {{{#!raw Traceback (most recent call last): File "www/cgi/new.py", line 51, in <module> skel(new_log) File "www/cgi/new.py", line 44, in skel raise '....' }}} $Python$ 預設的錯誤訊息只有輸出 exception 發生時,程式呼叫的次序,也就是 traceback (上例)。 traceback 只能指出錯誤發生的位置,卻沒有錯誤發生時的變數內容。在不知道變數內容的狀況下,有時我們無法理解程式為何會出錯,而產生疑惑,使的除錯更加困難。這樣的狀況並不少見,因此我很難加以忽視。例如,當我們透過 DB-API 執行 SQL command 時,錯誤原因的判定往往有賴於 command 的參數內容。例如: {{{#!python cx = sqlite3.connect("$test$.db") cur = cx.cursor() cur.execute('select * from uid = ?', (uid,)) }}} 當第三行執行時, $Python$ 通常只會告訴你錯誤發生在該行。然而,單就該行 query ,實在難以斷定錯誤的原因。很有可能 $database$ 裡的 uid 欄位是整數,而傳入的參數卻是字串,因而造成錯誤。這時若能保留 uid 變數的內容,將能很快速的界定錯誤的起因。 == 捕捉 == 因此,我們應該改寫 $Python$ 處理 exception 方式,保留錯誤發生時的變數內容。要做到這一點,最簡單的方式就是在程式的進入點,以 try...except... 捕捉所有的錯誤。如下例: {{{#!python if __name__ == '__main__': try: main() except: # exception handler pass }}} 當我們捕捉到錯誤時,我們可以透過 sys module 下的 exc_info() 取得 traceback 的內容,並據以取得所需的資訊。 {{{#!python import sys tp, val, tb = sys.exc_info() }}} * tp exception 的 type * val 是 raise 時,第二個參數 * tb 則保留了 exception 發生時的呼叫狀態和堆疊內容 例如 {{{#!python raise ValueError, 'invalid argument' }}} 捕捉到的內容為 * tp = ValueError * val = ValueError('invalid argument') 於是,我們就可以用下面的程式輸出錯誤內容 {{{#!python import sys tp, val, tb = sys.exc_info() print >> sys.stderr, '%s: %s' % (str(tp), str(val) }}} == traceback == traceback 保留程式執行當中,呼叫的次序,和 frame 的內容。透過 traceback ,我們能取得程式呼叫的流程,呼叫的 function 名稱,呼叫者的檔案名稱和行數,甚至是每一個 function 的 global 和 local namespace 的內容。exc_info() 傳回的 traceback 保存的是最外層 function call 的 frame , 也就是程式進入點所在的 module 。透過 traceback.tb_next 可以取得往內一層的 traceback ,可以取得另一個 frame 。所謂的內、外層是以程式呼叫的次序決定。 程式呼叫 A() , A() 呼叫 B() ,B() 再呼叫 $C$() 時,呼叫 A() 的程式是最外層,然後是 A() , 最內層是 $C$() 。每一次 function 呼叫時,都會建立一個 frame ,以儲存 function 執行時的狀態。因此,這時系統的狀態如下圖: [attach:traceback.png] exc_info() 傳回的是最外層,也就是右邊最上方的 traceback。透過該 traceback ,我們能取得每一層呼叫的 frame 。 frame 記錄執行的 function ,執行的位置,和執行時的 local 和 global namespace 。 == function 資訊 == 透過 frame 的 f_code 屬性,能取得 function 的相關資訊。例如, * frame.f_code.co_name 記錄 function 或 module 名稱 * frame.f_code.co_filename 記錄 function 所在的檔案 == 變數內容 == 透過 frame 的 f_locals 和 f_globals 可以取得 frame 執行時的 local 和 global namespace ,兩者皆為 dictionary 。透過該 dictionary ,我們可以取得所有變數的名稱和變數的值。例如,下面的 code 印出所有變數的內容。 {{{#!python import sys tp, val, tb = sys.exc_info() while tb: frame = tb.tb_frame print >> sys.stderr, 'locals = %s' % (repr(frame.f_locals)) print >> sys.stderr, 'globals = %s' % (repr(frame.f_globals)) print >> sys.stderr tb = tb.tb_next pass }}} 透過 traceback 的 tb_next 屬性,我們能一層接著一層,由外往內取得所有的變數內容。 == excepthook == 前面我們在程式的進入點,透過 try...except.... 語法,捕捉所有漏網的 exception 。這是一種方法,但如果你的系統有許多隻程式組成時,就必需不斷重複這些 code 。另一種方式是設定 excepthook ,任何未被捕獲的 exception ,最後就會導致程式結束。在這種情況下,程式結束之前會先呼叫 excepthook,我們可以設定 excepthook ,以改變錯誤訊息內容。如下: {{{#!python import sys def myhook(tp, val, tb): # ....... sys.excepthook = myhook }}} hook 的三個參數,正是 exc_info() 傳回的三個變數。 == 結論 == 程式難免會有意料之外的錯誤,最差的狀況就是把所有的錯誤隱藏起來。將錯誤忠實的記錄下,還是比較好的策略。透過將 exception 的發生,將 frame 次序,和變數的內容記錄下來,能很有效的改善除錯效率。據我過去的經驗,通常我們只需記錄最內層 frame 的 local namespace 就足夠了。但,如果空間不是問題,將所有 frame 的資訊記錄下來,那就更萬無一失了。
最後更新時間: 2007-12-08 23:41:14 CST |
引用
查詢:
COMMENTS: