本篇介紹如何在 Python 中使用 assert
敘述,協助程式設計者在開發階段偵測程式臭蟲(bugs)。
Python 的 assert
敘述是一個很實用的除錯輔助工具,可在程式開發階段協助程式設計者偵測不該發生的條件是否發生,藉此提升程式的穩定與可靠性。
assert
敘述使用方式assert
的語法如下:
assert 判斷條件[, 錯誤訊息]
如果 判斷條件
為 False
的時候,就會引發 AssertionError
例外,而 錯誤訊息
則是可有可無的參數,可用來指定要給使用者看的訊息,若 AssertionError
例外被觸發,則該訊息就會附加在例外的錯誤訊息中。
以下是一個簡單的 assert
使用範例,假設我們定義了一個函數 computePassRatio
,傳入測驗通過(passCount)與沒通過(failCount)的人數,然後計算出本次測驗的通過率,理論上每一的測驗的總人數一定都會大於零(若完全沒人參與,就根本沒辦法進行測驗),但是為了避免程式設計者不小心寫錯程式,所以我們在這裡使用 assert
額外對總人數進行檢查,確保總人數是大於零的:
# 計算測驗通過率 def computePassRatio(passCount, failCount): # 計算總人數 sumCount = passCount + failCount # 檢查總人數 assert sumCount > 0, "總人數必須大於零" # 計算通過率 return passCount / sumCount
如果這時候程式不小心寫錯,出現了完全沒有人進行測驗,卻要計算通過率的狀況,就會觸發例外:
# 錯誤的呼叫 computePassRatio(0, 0)
Traceback (most recent call last): File "assert_demo.py", line 13, in computePassRatio(0, 0) File "assert_demo.py", line 8, in computePassRatio assert sumCount > 0, "總人數必須大於零" AssertionError: 總人數必須大於零
assert
敘述的語法跟函數呼叫不同,不能加上括號,如果不小心加上括號,就會變成傳入一個 tuple 作為判斷式,這樣就會造成永遠都是 True
的判斷式,Python 3 直譯器若遇到永遠都是成立的 assert
敘述也會發出警告:
# 錯誤示範:誤用括號 assert(1 == 2, "錯誤訊息")
assert_brackets.py:1: SyntaxWarning: assertion is always true, perhaps remove parentheses? assert(1 == 2, "錯誤訊息")
assert
語法在實際執行時,會被 Python 直譯器轉換為類似這樣的程式碼:
if __debug__: if not 判斷條件: raise AssertionError(錯誤訊息)
這裡的全域變數 __debug__
是一個 Python 內建的旗標變數,普通情況下它的值會是 True
,但是當 Python 直譯器啟用了最佳化編譯選項時,__debug__
就會被設定為 False
。
而 Python 程式在檢查 assert
的判斷條件之前,會先檢查 __debug__
這個全域變數是否為 True
,若 __debug__
為 True
才會繼續檢查指定的判斷條件,也就是說在程式啟用了最佳化編譯選項後,所有的 assert
檢查都會被忽略。
我們可以在執行 Python 程式時加上 -O
或 -OO
參數來啟用最佳化編譯:
# 啟用基本最佳化編譯 python -O assert_demo.py # 啟用進階最佳化編譯 python -OO assert_demo.py
另一種啟用最佳化編譯的方式是設定 PYTHONOPTIMIZE
環境變數:
# 啟用基本最佳化編譯 export PYTHONOPTIMIZE=1 python assert_demo.py # 啟用進階最佳化編譯 export PYTHONOPTIMIZE=2 python assert_demo.py
assert
適用情況由於 assert
敘述只會在 __debug__
旗標變數設定為 True
的時候有作用,當程式布署至正式環境中時,所有 assert
敘述有可能就會被完全忽略,所以 assert
只適合用於開發與測試階段的各種確保性條件檢查(省略亦可正常運作的檢查),檢查程式本身有沒有寫錯(bugs),但不可以用來檢查輸入資料的正確性或是各種程式本身的邏輯判斷。
另外 assert
在進行條件判斷時也不可以產生任何邊際效應(更改了程式中的某些資料),因為如果在 assert
敘述中會對資料進行某些操作,而在正式執行階段又省略了所有 assert
敘述,就會產生很難釐清的程式臭蟲。以下是一個簡單的錯誤示範:
myList = [1, 2, 3] # 錯誤寫法:含有邊際效應的 assert 敘述 assert myList.pop() == 3 print(myList)
將這段指令搞儲存為 side_eff.py
之後,以不同的方式執行,卻會得到不同的結果:
# 正常執行
python side_eff.py
[1, 2]
# 啟用最佳化編譯 python -O side_eff.py
[1, 2, 3]
這就是由於 assert
敘述隱含邊際效應所造成的程式臭蟲,像這樣的程式臭蟲只會在正式執行階段出現,在開發與測試階段事不會發生的,在除錯上非常麻煩。
assert
敘述若 assert
敘述的內容過長,可以使用行尾反斜線的方式拆成多行撰寫:
# 以行尾反斜線分為多行撰寫 assert 123456789 == 0.123456789, \ "這是一條非常冗長的錯誤訊息。"