歡迎光臨
每天分享高質量文章

Python with提前退出:坑與解決方案

問題的起源

早些時候使用with實現了一版全域性行程鎖,希望實現以下效果:


全域性行程鎖本身不用多說,大部分都依靠外部的快取來實現的,redis上用的是setnx,有時候根據需要加上快取擊穿問題、隨機延後以防止對快取本身造成壓力。


當時同樣寫了單元測試來測試這段程式碼的有效性:


看起來非常完美地透過了。


這樣的一個全域性行程鎖是透過__enter__方法丟擲異常, __exit__方法中捕獲異常來實現的:


看起來還不錯,畢竟單元測試都過了。


但是,這樣的實現是有問題的:

原因在於__exit__ 的執行不是包在__enter__ 之外的,因此__enter__丟擲的異常,不會被__exit__捕獲。


上面的單元測試恰好透過,是因為其中有兩個with陳述句,外面的with 捕獲的其實是裡面的__enter__ 丟擲的異常


使用改進後的單元測試:


就會發現單元測試過不去了。


這個問題是我試圖使用with實現另一個邏輯:AB測試 時出現的,同樣是__enter__丟擲異常,__exit__ 試圖捕獲:


除錯沒有透過的單元測試的時候發現,丟擲異常後根本沒有執行到__enter__。


第一種解決方案


既然想明白了with的執行順序,那麼第一種解決方案就呼之欲出了:既然__exit__捕獲的異常在__enter__執行完成之後,那麼我們提供一個函式確認一下就可以了,把ABContext實現改成這樣:


使用的時候:


但這樣的解決方法並不優雅,萬一使用這個ABContext的時候忘記用ensure方法了,那麼就等於完全沒用這個Context方法,太容易失誤了,而且程式碼也失去了Pythonic的性質。


第二種解決方法


翻了一下contextlib的標準庫檔案,發現有一個已經廢棄的函式:contextlib.nested


可以執行多個背景關係:


這個廢棄的特性在Python2.7之後,可以直接由with關鍵字執行,形如:


這個特性還不錯,根據__enter__的執行順序的話,那麼我們可以實現一個由第一個 context的__exit__來捕獲,第二個context的__enter__來丟擲異常,

如同這樣:


結合前面我們實現的ABContext的使用是這樣的:


good,單元測試就這樣過了!


能不能再給力點?


確實,在with裡要寫倆context有點蛋疼,並不是特別優雅,能不能還是回到最初的那種用法:我們只用寫一條context,這一個context做到了兩個context的事情?


要是nested那個函式還在就好了。。要的其實就是它的功能。

Python3.1之後contextlib提供了一個ExitStack的功能來提供一個模擬的功能,但試了一下發現,實際上只呼叫了__enter__方法,但沒有做對應的異常捕獲。


第三種解決方案


哈哈哈哈把自己繞到圈子裡去了,想了一下,同樣是一個縮排的程式碼塊,為什麼不能用if來解決呢!不就是個:

的問題。。。


TIL


總之學到了contextlib裡的一些有用的函式和裝飾器,也第一次發現with可以放個context。

雖然放多個context的動態構造還有待研究,with 後面的程式碼塊也不能填一個元組或者串列。。惆悵。。


作者:豬了個去

來源:https://zhuanlan.zhihu.com/p/32617838



————近期開班————

馬哥聯合BAT、豆瓣等一線網際網路Python開發達人,根據目前企業需求的Python開發人才進行了深度定製,加入了大量一線網際網路公司:大眾點評、餓了麼、騰訊等生產環境真是專案,課程由淺入深,從Python基礎到Python高階,讓你融匯貫通Python基礎理論,手把手教學讓你具備Python自動化開發需要的前端介面開發、Web框架、大監控系統、CMDB系統、認證堡壘機、自動化流程平臺六大實戰能力,讓你從0開始蛻變成Hold住年薪20萬的Python自動化開發人才

10期面授班:2018年03月05號(北京)

09期網路班:騰訊課堂隨到隨學網路

掃描二維碼領取學習資料

更多Python好文請點選【閱讀原文】哦

↓↓↓

贊(0)

分享創造快樂

© 2024 知識星球   網站地圖