Python中何時使用斷言 assert


 

 

 

使用斷言的最佳時機偶爾會被提起,通常是因為有人誤用,因此我覺得有必要寫一篇文章來闡述一下什么時候應該用斷言,為什么應該用,什么時候不該用。

對那些沒有意識到用斷言的最佳時機的人來說,Python的斷言就是檢測一個條件,如果條件為真,它什么都不做;反之它觸發一個帶可選錯誤信息的AssertionError。如下例所示:

很多人將斷言作為當傳遞了錯誤的參數值時的一種快速而簡便的觸發異常的方式。但實際上這是錯誤的,而且是非常危險的錯誤,原因有兩點。首先,AssertionError通常是在測試函數參數時給出的錯誤。你不會像下面這樣編碼:

你應該用TypeError來替代,“斷言”解決了錯誤的異常類型。

但是對斷言來說更危險也更糾結的是:如果你執行Python時使用了-O或-OO優化標識,這能夠通過編譯卻從來不會被執行,實際上就是說並不能保證斷言會被執行。當恰當地使用了斷言,這非常好的,但當不恰當地使用了斷言,在使用-O標識執行時它將導致代碼被徹底中斷。

那么我們什么時候應該使用斷言呢?如果沒有特別的目的,斷言應該用於如下情況:

  • 防御性的編程
  • 運行時對程序邏輯的檢測
  • 合約性檢查(比如前置條件,后置條件)
  • 程序中的常量
  • 檢查文檔

(斷言也可以用於代碼測試,用作一個做事毛手毛腳的開發人員的單元測試,只要能你接受當使用-O標志時這個測試什么都不做。我有時也會在代碼中用"assert Fasle"來對還沒有實現的分支作標記,當然我希望他們失敗。如果稍微更細節一些,或許觸發NotImplementedError是更好的選擇)

因為程序員是對於代碼正確性表現出的信心不同,因此對於什么時候使用斷言的意見各不相同。如果你確信代碼是正確的,那么斷言沒有任何意義,因為它們從不會失敗,因此你可以放心地移除它們。如果你確信它們會失敗(例如對用戶輸入的數據的檢測),你不敢用斷言,這樣編譯就能通過,但你跳過了你的檢查。

在以上兩種情況之間的情況就顯得特別有趣了,那就是當你相信代碼是正確的,但又不是特別確定的時候。或許你忘記了一些奇怪的邊角情況(因為我們都是人),在這種情況下,額外的運行時檢查將幫助你盡可能早地捕獲錯誤,而不是寫了一大堆代碼之后。

(這就是為什么使用斷言的時機會不同。因為我們對代碼正確性的信息不同,對於一個人有用的斷言,對於另一個人來說卻是無用的運行時測試。)

另一個斷言用得好的地方就是檢查程序中的不變量。一個不變量是一些你能相信為真的條件,除非一個缺陷導致它變成假。如果有一個缺陷,越早發現越好,因此我們需要對其進行測試,但我們不想因為這些測試而影響代碼執行速度。因此采用斷言,它能在開發時生效而在產品中失效。

一個關於不變量的例子可能是這樣的情況。如果你的函數在開始的時候期望一個打開的數據庫連接,並且在函數返回后該數據庫連接依然是打開的,這是一個函數的不變量:

斷言也是一個很好的檢查點注釋。為了替代如下注釋:

#當我們執行到這里,我們知道n>2

你可以確保在運行時用以下斷言:

斷言也是一種防御性的編程形式。你不是在防范當前代碼發生錯誤,而防范由於以后的代碼變更發生錯誤。理想情況下,單元測試應該直到這個作用,但是讓我們面對這樣一個現實:即使存在單元測試,他們在通常情況下也不是很完備。內建的機器人可能沒有工作,但數周以來也沒有人注意到它,或者人們在提交代碼之前忘記了執行測試。內部檢查將是防止錯誤滲入的另一道防線,尤其對於那些悄悄地失敗,但會引起代碼功能錯誤並返回錯誤結果的情況有效。

假設你有一系列的if...elif代碼塊,你預先知道變量期望的值:

假設這段代碼現在完全正確。但它會一直正確嗎?需求變更,代碼變更。如果需求變為允許target = w,並關聯到run_w_code,那將會發生什么情況?如果我們變更了設置target的代碼,但是忘記了改變這個代碼塊,它就會錯誤地調用run_z_code(),錯誤就會發生。對於這段代碼最好的方法就是編寫一些防御性的檢查,這樣它的執行,即使在變更以后,要么正確,要么馬上失敗。

在代碼開始添加注釋是個好的開端,但是人們都不太喜歡讀和更新這些注釋,這些注釋會很快變得過時。但對於斷言,我們可以同時對這塊代碼編寫文檔,如果這些斷言被違反了,會直接引起一個簡單而又直接的失敗。

這里的斷言同時用於防御性編程和檢查文檔。我認為這是最優的解決方案:

這誘使開發者去不理代碼,移除像value ==c這類不必要的測試,以及RuntimeError的“死代碼”。另外,當"unexpected error"錯誤發生時這個消息將非常窘迫,確實會發生。

合約式設計是斷言另一個用得好的地方。在合約式設計中,我們認為函數與其他調用者遵循合約,例如像這樣的情況:

“如果你傳給我一個非空字符串,我保證返回轉換成大寫的首字母。”

如果合約被破壞了,不管是被函數本身還是調用者,這都會產生缺陷。我們說這個函數需要有前置條件(對期望的參數的限制)和后置條件(對返回結果的約束)。因此這個函數可能是這樣的:

合約式設計的目的是,在一個正確的程序里,所有的前置條件和后置條件都將得到處理。這是斷言的經典應用,自(這個想法持續)我們發布無缺陷的程序並且將其放入產品,程序將是正確的並且我們可以放心地移除檢查。

這里是我建議不使用斷言的情況:

*不要用於測試用戶提供的數據,或者那些需要在所有情況下需要改變檢查的地方

*不要用於檢查你認為在通常使用中可能失敗的地方。斷言用於非常特別的失敗條件。你的用戶絕不看到一個AssertionError,如果看到了,那就是個必須修復的缺陷。

*特別地不要因為斷言只是比一個明確的測試加一個觸發異常矮小而使用它。斷言不是懶惰的代碼編寫者的捷徑。

*不要將斷言用於公共函數庫輸入參數的檢查,因為你不能控制調用者,並且不能保證它不破壞函數的合約。

*不要將斷言用於你期望修改的任何錯誤。換句話,你沒有任何理由在產品代碼捕獲一個AssertionError異常。

*不要太多使用斷言,它們使代碼變得晦澀難懂。

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM