分析在Python中何種情況下需要使用斷言
這個(gè)問題是如何在一些場(chǎng)景下使用斷言表達(dá)式,通常會(huì)有人誤用它,所以我決定寫一篇文章來說明何時(shí)使用斷言,什么時(shí)候不用。
為那些還不清楚它的人,Python的assert是用來檢查一個(gè)條件,如果它為真,就不做任何事。如果它為假,則會(huì)拋出AssertError并且包含錯(cuò)誤信息。例如:
py> x = 23 py> assert x > 0, "x is not zero or negative" py> assert x%2 == 0, "x is not an even number" Traceback (most recent call last): File "", line 1, in AssertionError: x is not an even number
很多人用assert作為一個(gè)很快和容易的方法來在參數(shù)錯(cuò)誤的時(shí)候拋出異常。但這樣做是錯(cuò)的,非常錯(cuò)誤,有兩個(gè)原因。首先AssertError不是在測(cè)試參數(shù)時(shí)應(yīng)該拋出的錯(cuò)誤。你不應(yīng)該像這樣寫代碼:
if not isinstance(x, int):
raise AssertionError("not an int")
你應(yīng)該拋出TypeError的錯(cuò)誤,assert會(huì)拋出錯(cuò)誤的異常。
但是,更危險(xiǎn)的是,有一個(gè)關(guān)于assert的困擾:它可以被編譯好然后從來不執(zhí)行,如果你用 –O 或 –oo 選項(xiàng)運(yùn)行Python,結(jié)果不保證assert表達(dá)式會(huì)運(yùn)行到。當(dāng)適當(dāng)?shù)氖褂胊ssert時(shí),這是未來,但是當(dāng)assert不恰當(dāng)?shù)氖褂脮r(shí),它會(huì)讓代碼用-O執(zhí)行時(shí)出錯(cuò)。
那什么時(shí)候應(yīng)該使用assert?沒有特定的規(guī)則,斷言應(yīng)該用于:
- 防御型的編程
- 運(yùn)行時(shí)檢查程序邏輯
- 檢查約定
- 程序常量
- 檢查文檔
(在測(cè)試代碼的時(shí)候使用斷言也是可接受的,是一種很方便的單元測(cè)試方法,你接受這些測(cè)試在用-O標(biāo)志運(yùn)行時(shí)不會(huì)做任何事。我有時(shí)在代碼里使用assert False來標(biāo)記沒有寫完的代碼分支,我希望這些代碼運(yùn)行失敗。盡管拋出NotImplementedError可能會(huì)更好。)
關(guān)于斷言的意見有很多,因?yàn)樗艽_保代碼的正確性。如果你確定代碼是正確的,那么就沒有用斷言的必要了,因?yàn)樗麄儚膩聿粫?huì)運(yùn)行失敗,你可以直接移除這些斷言。如果你確定檢查會(huì)失敗,那么如果你不用斷言,代碼就會(huì)通過編譯并忽略你的檢查。
在以上兩種情況下會(huì)很有意思,當(dāng)你比較肯定代碼但是不是絕對(duì)肯定時(shí)??赡苣銜?huì)錯(cuò)過一些非常古怪的情況。在這個(gè)情況下,額外的運(yùn)行時(shí)檢查能幫你確保任何錯(cuò)誤都會(huì)盡早地被捕捉到。
另一個(gè)好的使用斷言的方式是檢查程序的不變量。一個(gè)不變量是一些你需要依賴它為真的情況,除非一個(gè)bug導(dǎo)致它為假。如果有bug,最好能夠盡早發(fā)現(xiàn),所以我們?yōu)樗M(jìn)行一個(gè)測(cè)試,但是又不想減慢代碼運(yùn)行速度。所以就用斷言,因?yàn)樗茉陂_發(fā)時(shí)打開,在產(chǎn)品階段關(guān)閉。
一個(gè)非變量的例子可能是,如果你的函數(shù)希望在它開始時(shí)有數(shù)據(jù)庫(kù)的連接,并且承諾在它返回的時(shí)候仍然保持連接,這就是函數(shù)的不變量:
def some_function(arg): assert not DB.closed() ... # code goes here assert not DB.closed() return result
斷言本身就是很好的注釋,勝過你直接寫注釋:
# when we reach here, we know that n > 2
你可以通過添加斷言來確保它:
assert n > 2
斷言也是一種防御型編程。你不是讓你的代碼防御現(xiàn)在的錯(cuò)誤,而是防止在代碼修改后引發(fā)的錯(cuò)誤。理想情況下,單元測(cè)試可以完成這樣的工作,可是需要面對(duì)的現(xiàn)實(shí)是,它們通常是沒有完成的。人們可能在提交代碼前會(huì)忘了運(yùn)行測(cè)試代碼。有一個(gè)內(nèi)部檢查是另一個(gè)阻擋錯(cuò)誤的防線,尤其是那些不明顯的錯(cuò)誤,卻導(dǎo)致了代碼出問題并且返回錯(cuò)誤的結(jié)果。
加入你有一些if…elif 的語句塊,你知道在這之前一些需要有一些值:
# target is expected to be one of x, y, or z, and nothing else. if target == x: run_x_code() elif target == y: run_y_code() else: run_z_code()
假設(shè)代碼現(xiàn)在是完全正確的。但它會(huì)一直是正確的嗎?依賴的修改,代碼的修改。如果依賴修改成 target = w 會(huì)發(fā)生什么,會(huì)關(guān)系到run_w_code函數(shù)嗎?如果我們改變了代碼,但沒有修改這里的代碼,可能會(huì)導(dǎo)致錯(cuò)誤的調(diào)用 run_z_code 函數(shù)并引發(fā)錯(cuò)誤。用防御型的方法來寫代碼會(huì)很好,它能讓代碼運(yùn)行正確,或者立馬執(zhí)行錯(cuò)誤,即使你在未來對(duì)它進(jìn)行了修改。
在代碼開頭的注釋很好的一步,但是人們經(jīng)常懶得讀或者更新注釋。一旦發(fā)生這種情況,注釋會(huì)變得沒用。但有了斷言,我可以同時(shí)對(duì)代碼塊的假設(shè)書寫文檔,并且在它們違反的時(shí)候觸發(fā)一個(gè)干凈的錯(cuò)誤
assert target in (x, y, z) if target == x: run_x_code() elif target == y: run_y_code() else: assert target == z run_z_code()
這樣,斷言是一種防御型編程,同時(shí)也是一種文檔。我想到一個(gè)更好的方案:
if target == x:
run_x_code()
elif target == y:
run_y_code()
elif target == z:
run_z_code()
else:
# This can never happen. But just in case it does...
raise RuntimeError("an unexpected error occurred")
按約定進(jìn)行設(shè)計(jì)是斷言的另一個(gè)好的用途。我們想象函數(shù)與調(diào)用者之間有個(gè)約定,比如下面的:
“如果你傳給我一個(gè)非空字符串,我保證傳會(huì)字符串的第一個(gè)字母并將其大寫。”
如果約定被函數(shù)或調(diào)用這破壞,代碼就會(huì)出問題。我們說函數(shù)有一些前置條件和后置條件,所以函數(shù)就會(huì)這么寫:
def first_upper(astring): assert isinstance(astring, str) and len(astring) > 0 result = astring[0].upper() assert isinstance(result, str) and len(result) == 1 assert result == result.upper() return result
按約定設(shè)計(jì)的目標(biāo)是為了正確的編程,前置條件和后置條件是需要保持的。這是斷言的典型應(yīng)用場(chǎng)景,因?yàn)橐坏┪覀儼l(fā)布了沒有問題的代碼到產(chǎn)品中,程序會(huì)是正確的,并且我們能安全的移除檢查。
下面是我建議的不要用斷言的場(chǎng)景:
- 不要用它測(cè)試用戶提供的數(shù)據(jù)
- 不要用斷言來檢查你覺得在你的程序的常規(guī)使用時(shí)會(huì)出錯(cuò)的地方。斷言是用來檢查非常罕見的問題。你的用戶不應(yīng)該看到任何斷言錯(cuò)誤,如果他們看到了,這是一個(gè)bug,修復(fù)它。
- 有的情況下,不用斷言是因?yàn)樗染_的檢查要短,它不應(yīng)該是懶碼農(nóng)的偷懶方式。
- 不要用它來檢查對(duì)公共庫(kù)的輸入?yún)?shù),因?yàn)樗荒芸刂普{(diào)用者,所以不能保證調(diào)用者會(huì)不會(huì)打破雙方的約定。
- 不要為你覺得可以恢復(fù)的錯(cuò)誤用斷言。換句話說,不用改在產(chǎn)品代碼里捕捉到斷言錯(cuò)誤。
- 不要用太多斷言以至于讓代碼很晦澀。
- Python selenium實(shí)現(xiàn)斷言3種方法解析
- Python unittest單元測(cè)試框架及斷言方法
- python異常處理、自定義異常、斷言原理與用法分析
- Python3 assert斷言實(shí)現(xiàn)原理解析
- python3 assert 斷言的使用詳解 (區(qū)別于python2)
- Python TestCase中的斷言方法介紹
- python 實(shí)現(xiàn)selenium斷言和驗(yàn)證的方法
- Python斷言assert的用法代碼解析
- Python中斷言Assertion的一些改進(jìn)方案
- Python常用斷言函數(shù)實(shí)例匯總
相關(guān)文章
在python中實(shí)現(xiàn)導(dǎo)入一個(gè)需要傳參的模塊
這篇文章主要介紹了在python中實(shí)現(xiàn)導(dǎo)入一個(gè)需要傳參的模塊,具有很好的參考價(jià)值,希望可以給大家一個(gè)參考,以后在遇到這種的情況的時(shí)候,知道如何應(yīng)對(duì)2021-05-05
詳解Python+OpenCV進(jìn)行基礎(chǔ)的圖像操作
OpenCV是一個(gè)用于計(jì)算機(jī)視覺和圖像操作的免費(fèi)開源庫(kù),有數(shù)千種優(yōu)化的算法和函數(shù)用于各種圖像操作。本文將使用OpenCV在Python中進(jìn)行一些圖像操作,感興趣的可以學(xué)習(xí)一下2022-02-02
Python棧算法的實(shí)現(xiàn)與簡(jiǎn)單應(yīng)用示例
這篇文章主要介紹了Python棧算法的實(shí)現(xiàn)與簡(jiǎn)單應(yīng)用,簡(jiǎn)單講述了棧的原理并結(jié)合實(shí)例形式給出了基于棧實(shí)現(xiàn)的進(jìn)制轉(zhuǎn)換與括號(hào)匹配等相關(guān)使用技巧,需要的朋友可以參考下2017-11-11
Python使用正則表達(dá)式實(shí)現(xiàn)爬蟲數(shù)據(jù)抽取
這篇文章主要介紹了Python使用正則表達(dá)式實(shí)現(xiàn)爬蟲數(shù)據(jù)抽取,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08
python人工智能tensorflow函數(shù)tf.layers.dense使用方法
這篇文章主要介紹了python人工智能tensorflow函數(shù)tf.layers.dense的使用方法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05
詳談Python2.6和Python3.0中對(duì)除法操作的異同
下面小編就為大家?guī)硪黄斦凱ython2.6和Python3.0中對(duì)除法操作的異同。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-04-04

