Python 中何時使用斷言?

賤聖OMG發表於2014-09-02

這個問題是如何在一些場景下使用斷言表示式,通常會有人誤用它,所以我決定寫一篇文章來說明何時使用斷言,什麼時候不用。

為那些還不清楚它的人,Python的assert是用來檢查一個條件,如果它為真,就不做任何事。如果它為假,則會丟擲AssertError並且包含錯誤資訊。例如:

很多人用assert作為一個很快和容易的方法來在引數錯誤的時候丟擲異常。但這樣做是錯的,非常錯誤,有兩個原因。首先AssertError不是在測試引數時應該丟擲的錯誤。你不應該像這樣寫程式碼:

你應該丟擲TypeError的錯誤,assert會丟擲錯誤的異常。

但是,更危險的是,有一個關於assert的困擾:它可以被編譯好然後從來不執行,如果你用 –O 或 –oo 選項執行Python,結果不保證assert表示式會執行到。當適當的使用assert時,這是未來,但是當assert不恰當的使用時,它會讓程式碼用-O執行時出錯。

那什麼時候應該使用assert?沒有特定的規則,斷言應該用於:

  • 防禦型的程式設計
  • 執行時檢查程式邏輯
  • 檢查約定
  • 程式常量
  • 檢查文件

(在測試程式碼的時候使用斷言也是可接受的,是一種很方便的單元測試方法,你接受這些測試在用-O標誌執行時不會做任何事。我有時在程式碼裡使用assert False來標記沒有寫完的程式碼分支,我希望這些程式碼執行失敗。儘管丟擲NotImplementedError可能會更好。)

關於斷言的意見有很多,因為它能確保程式碼的正確性。如果你確定程式碼是正確的,那麼就沒有用斷言的必要了,因為他們從來不會執行失敗,你可以直接移除這些斷言。如果你確定檢查會失敗,那麼如果你不用斷言,程式碼就會通過編譯並忽略你的檢查。

在以上兩種情況下會很有意思,當你比較肯定程式碼但是不是絕對肯定時。可能你會錯過一些非常古怪的情況。在這個情況下,額外的執行時檢查能幫你確保任何錯誤都會盡早地被捕捉到。

另一個好的使用斷言的方式是檢查程式的不變數。一個不變數是一些你需要依賴它為真的情況,除非一個bug導致它為假。如果有bug,最好能夠儘早發現,所以我們為它進行一個測試,但是又不想減慢程式碼執行速度。所以就用斷言,因為它能在開發時開啟,在產品階段關閉。

一個非變數的例子可能是,如果你的函式希望在它開始時有資料庫的連線,並且承諾在它返回的時候仍然保持連線,這就是函式的不變數:

斷言本身就是很好的註釋,勝過你直接寫註釋:

# when we reach here, we know that n > 2

你可以通過新增斷言來確保它:

assert n > 2

斷言也是一種防禦型程式設計。你不是讓你的程式碼防禦現在的錯誤,而是防止在程式碼修改後引發的錯誤。理想情況下,單元測試可以完成這樣的工作,可是需要面對的現實是,它們通常是沒有完成的。人們可能在提交程式碼前會忘了執行測試程式碼。有一個內部檢查是另一個阻擋錯誤的防線,尤其是那些不明顯的錯誤,卻導致了程式碼出問題並且返回錯誤的結果。

加入你有一些if…elif 的語句塊,你知道在這之前一些需要有一些值:

假設程式碼現在是完全正確的。但它會一直是正確的嗎?依賴的修改,程式碼的修改。如果依賴修改成 target = w 會發生什麼,會關係到run_w_code函式嗎?如果我們改變了程式碼,但沒有修改這裡的程式碼,可能會導致錯誤的呼叫 run_z_code 函式並引發錯誤。用防禦型的方法來寫程式碼會很好,它能讓程式碼執行正確,或者立馬執行錯誤,即使你在未來對它進行了修改。

在程式碼開頭的註釋很好的一步,但是人們經常懶得讀或者更新註釋。一旦發生這種情況,註釋會變得沒用。但有了斷言,我可以同時對程式碼塊的假設書寫文件,並且在它們違反的時候觸發一個乾淨的錯誤

這樣,斷言是一種防禦型程式設計,同時也是一種文件。我想到一個更好的方案:

按約定進行設計是斷言的另一個好的用途。我們想象函式與呼叫者之間有個約定,比如下面的:

“如果你傳給我一個非空字串,我保證傳會字串的第一個字母並將其大寫。”

如果約定被函式或呼叫這破壞,程式碼就會出問題。我們說函式有一些前置條件和後置條件,所以函式就會這麼寫:

按約定設計的目標是為了正確的程式設計,前置條件和後置條件是需要保持的。這是斷言的典型應用場景,因為一旦我們釋出了沒有問題的程式碼到產品中,程式會是正確的,並且我們能安全的移除檢查。

下面是我建議的不要用斷言的場景:

  • 不要用它測試使用者提供的資料
  • 不要用斷言來檢查你覺得在你的程式的常規使用時會出錯的地方。斷言是用來檢查非常罕見的問題。你的使用者不應該看到任何斷言錯誤,如果他們看到了,這是一個bug,修復它。
  • 有的情況下,不用斷言是因為它比精確的檢查要短,它不應該是懶碼農的偷懶方式。
  • 不要用它來檢查對公共庫的輸入引數,因為它不能控制呼叫者,所以不能保證呼叫者會不會打破雙方的約定。
  • 不要為你覺得可以恢復的錯誤用斷言。換句話說,不用改在產品程式碼裡捕捉到斷言錯誤。
  • 不要用太多斷言以至於讓程式碼很晦澀。

相關文章