來源:麥叔程式設計作者:麥叔本文包括以下內容:1. 一個檔案操作的bug2. 這個問題可以很嚴重!3. 修改Bug方法1 - 加上關閉語句4. 修改Bug方法2 - 用異常捕捉5. 修改bug方法3 - 上
2021-06-29 15:07:31
來源:麥叔程式設計
作者:麥叔
本文包括以下內容:
1. 一個檔案操作的bug2. 這個問題可以很嚴重!3. 修改Bug方法1 - 加上關閉語句4. 修改Bug方法2 - 用異常捕捉5. 修改bug方法3 - 上下文管理器6. 深入剖析Context Manager原理7. 管理其他資源8. 其他寫法9. 自己實現Context Manager - 類10. 自己實現Context Manager - 裝飾器1. 一個檔案操作的bug
下面的程式碼開啟一個檔案,寫入一些內容,但是它有個bug,你看看能否看出來?
f = open('maishu.txt', 'a')total = int(input('輸入總分數'))count = int(input('輸入科目數'))average = total/countf.write(f'總分:{total}n')f.write(f'平均分:{average}')
它忘記關閉檔案了!
在這個小的例子中,其實無傷大雅,當python程序結束的時候這個檔案會自動被釋放。但在大型的多執行緒程式中,會造成這個檔案被長期非法佔有,其他執行緒無法訪問。
你是否碰到過你想刪除檔案,但是電腦不讓你刪除的情況?那就是因為檔案被某個程序佔用,那個程序一直沒有關閉檔案。有可能是程式設計師的bug造成的,有可能是故意不讓你刪除(流氓軟體)。
2. 這個問題可以很嚴重!
這個關閉檔案的問題,可以擴展到關閉資料庫連結,管理網路連結,釋放鎖等。
這個bug可不是個小問題!
忘記釋放資料庫連結,或者忘記關閉檔案,造成系統資源過渡佔用,讓大型系統崩潰的例子在工作中我就碰到過很多次,有的還是老司機犯的錯誤!
有時候問題不嚴重,相關程式設計師被友善的批評一下就行了;有時候會造成一個團隊的人加班排查問題,這可能會影響這個程式設計師的獎金和加薪!甚至成為大家嫌棄的物件!
3. 修改Bug方法1 - 加上關閉語句
把程式修改一下吧,加上關閉操作:
f = open('maishu.txt', 'a')total = int(input('輸入總分數'))count = int(input('輸入科目數'))average = total/countf.write(f'總分:{total}n')f.write(f'平均分:{average}')print('關閉檔案')f.close()
這樣總可以了吧?非也!
如果你輸入的分數不是一個整數(比如maishu),或者輸入的科目數是0,這段程式都會報錯,從而導致最後的close語句不會被執行。
另外如果程式中有很多if..else的分支,很容易在某個分支中忘記呼叫close()函數。
4. 修改Bug方法2 - 用異常捕捉
再把程式修改一下,加上異常處理:
f = open('maishu.txt', 'a')try:total = int(input('輸入總分數')) count = int(input('輸入科目數')) average = total/count f.write(f'總分:{total}n') f.write(f'平均分:{average}')except Exception as err: print(f'出錯了:{err}')finally: print('關閉檔案') f.close()
這樣基本可以了,但程式碼麻煩又臃腫,稍微經驗欠缺的程式設計師都可能會忘記。
有沒有優雅又健壯點的方法呢?
5. 修改bug方法3 - 上下文管理器
更好的寫法是:
with open('maishu.txt', 'a') as f:f.write('1234') total = int(input('輸入總分數')) count = int(input('輸入科目數')) average = total/count f.write(f'總分:{total}n') f.write(f'平均分:{average}')
這樣寫簡單優雅,不用自己close(),不管程式是否報錯,python直譯器會確保幫你關閉檔案。
如果你只想知道上下文管理器怎麼使用,再仔細讀一下本文第8小節就夠了。
如果你想深入學習,繼續通讀全文。
6. 深入剖析Context Manager原理
因為資源管理的問題對大型程式很重要,所以python從語法上引入了Context Manager的概念。
本文後面可能會把資源和資源管理器兩個詞混著用,在本文中它們是相同的。
我們來看一下這段程式碼,理解語法的核心:
with open('maishu.txt', 'a') as f:f.write('1234')
上下文管理器的語法是:
with 被管理的資源 as 資源變數名f:對f各種操作
這個例子中,資源管理器的語法是:
with ... as
被管理的資源可以是檔案,或者資料庫連結,網路連結,或者我們自定義資源。本文後面會實現一個自己的資源。
所謂的資源管理器必須有兩個函數:
__enter__
__exit__
再來看看這段程式碼:
with open('maishu.txt', 'a') as f:f.write('1234')
open('maishu.txt', 'a')
返回了一個檔案物件file。它是一個資源管理器,可以被
with...as
管理,因為它有上面說的兩個函數:
在第1行,Python直譯器呼叫file的enter函數,這個函數返回一個物件,Python把這個物件命名為f。在這個例子中,file的enter函數只是簡單返回了自己。
第2行執行結束後,Python直譯器會呼叫file物件的exit函數。在這個函數中會關閉檔案。就算中間的被保護的程式碼運行出錯了,Python直譯器也會確保呼叫exit函數。
再來理一下:
Context Manager的核心就是__enter__和__exit__函數Python會自動幫你呼叫這兩個函數,確保它們在適當的時候被呼叫。我們可以在__enter__函數中做資源的初始化,在__exit__函數中做資源的回收。具體要做什麼是實現Context Manager的程式設計師確定的。這就是Context Manager的核心原理了。如果覺得有點繞,建議多看兩遍。
接下來我們會通過一個例項,自己實現一個Context Manager來加深理解。
7. 管理其他資源
上下文管理器Context Manager提供了一種可靠的資源管理機制,通過with語法,Python直譯器先呼叫資源的__enter__方法確保資源具備可以執行的條件,在程式碼執行完成或者出現異常後再呼叫__exit__方法確保有效釋放資源。
這種資源可以是檔案(前面的例子),可以是資料庫連結,可以是鎖,可以是網路連線。我們分別來看一個例子:
1)資料庫連結
from mysql_conn import get_mysql_connwith get_mysql_conn(db='mytestdb') as conn:df = pd.read_sql('SELECT * FROM mytable', conn)
2)鎖
在檔案的例子中,__enter__方法沒有做有價值的工作,只是返回了檔案本身。
在下面的這個鎖的例子中,__enter__方法很重要,它要呼叫lock.acqure()確保獲得了鎖才會執行程式碼,這是多執行緒協同的程式的關鍵。
import threadingwith threading.Lock() as lock:print('python會自動呼叫lock.acquire()') print('只有獲得了鎖才會執行本程式碼')
3)網路連線
import requestswith requests.Session() as session:session.get('http://httpbin.org/get')
8. 其他寫法
除了使用with .. as,也可以不用as:
f = open('maishu.txt', 'a')
with f:f.write('1234')
這裡先創建檔案變數,然後在用資源管理器管理,因為資源管理器的核心是保護一種資源,這種資源可以是提前創建好的,或者外面參數傳過來的。
其中的with .. as只不過用as給資源變數一個新的名字而已,這樣寫也是可以的:
def case4():
f = open('maishu.txt', 'a')with f as file: file.write('case4n')
希望到這裡你理解:關鍵在於with,as只是一個重新命名,只是一個點綴。
除此之外,with還可以一次性保護多個資源:
with open('maishu1.txt', 'w') as f1, open('maishu2.txt', 'w') as f2:f1.write('我是f1') f2.write('去你的f1,我是f2')
不是所有的資源都可以被with管理,資源必須提供了兩個方法,那就是__enter__和__exit__,下面我們通過一個例子,來創建自己的可以被with管理的資源。
有兩種方法實現可以被with管理的資源,一種是創建一個類,另外一種是使用裝飾器函數。
9. 自己實現Context Manager - 類
看這樣一個場景:我的項目目錄下有多個子資料夾,我要用程式:
進入子資料夾在子資料夾中做一些操作,比如列印檔案列表完成後必須回到原資料夾,否則下次進入子資料夾會因為路徑問題而報錯
這個每次先進入子資料夾,最後回到主資料夾的場景,就是一個很好的資源管理器的例子:
import osclass EnterFolder():# 初始化傳入必要的參數:要進入的資料夾 def __init__(self, folder): self.folder = folder # enter做初始化工作,也就是進入資料夾,enter不能有額外參數 def __enter__(self): os.chdir(self.folder) # exit做清理工作,也就是退回到上一級資料夾,它後面三個參數是當異常發生的時候傳入異常資訊 def __exit__(self, type, value, traceback): os.chdir('..')
這個EnterFolder就是一個資源管理器,它包含兩個必要的方法,確保會進入資料夾和退出資料夾。
有了它以後,寫資料夾操作的程式碼就很清爽也很安全了:
with EnterFolder('sf1'):
for f in os.listdir():print(f)with EnterFolder('sf2'): for f in os.listdir(): print(f)
這裡我們不需要as 因為這個場景不需要資源管理器傳變數給程式碼塊。
10. 自己實現Context Manager - 裝飾器
用類寫資源管理器比較直觀,容易理解。但也比較麻煩,所以Python提供了用裝飾器方法實現資源管理器,只要一個函數就搞定了:
import os#引入裝飾器方法from contextlib import contextmanager#應用裝飾器@contextmanagerdef enter_folder(folder):os.chdir(folder) yield os.chdir('..')with enter_folder('sf1'): for f in os.listdir(): print(f)with enter_folder('sf2'): for f in os.listdir(): print(f)
要理解上面的程式碼,你需要對裝飾器和生成器都有一點了解。這也屬於兩個進階話題,有興趣請在本文評論區留言,後面我可能會出相關話題的文章。
上面的函數實現了和類一樣的效果,關鍵的程式碼在第6行:yield
yield之前的程式碼做初始化工作,也就相當於前面的__enter__方法yield會暫定enter_folder去執行程式碼塊中的程式碼yield之後的程式碼做清理工作,相當於前面的__exit__方法
相關文章
來源:麥叔程式設計作者:麥叔本文包括以下內容:1. 一個檔案操作的bug2. 這個問題可以很嚴重!3. 修改Bug方法1 - 加上關閉語句4. 修改Bug方法2 - 用異常捕捉5. 修改bug方法3 - 上
2021-06-29 15:07:31
在6月份將要結束時相機市場好戲不斷,今天佳能釋出了RF卡口最後一支小三元鏡頭——RF14-35mm F4 L IS USM。佳能RF14-35mm F4 L IS USM是一款恆定F4光圈超廣變焦鏡頭,9片光圈葉
2021-06-29 14:47:16
佳能宣佈推出RF超廣角變焦鏡頭新品RF14-35mm F4 L IS USM。新鏡頭憑藉EOS R系統的大卡口直徑、短後對焦距離的優勢,採用了多種特殊鏡片和先進鍍膜技術,在實現小型輕量化設計同
2021-06-29 14:46:44
實施鄉村振興戰略,加快推進數字鄉村建設,是黨的十九大作出的重大決策,也是「十四五」規劃和遠景目標的重要部署。廣東移動堅持黨建引領,為群眾辦實事,集中力量做好「三農」服務工
2021-06-29 14:46:04
前段時間,微軟推出了windows11,微軟表示,只要是win10系統,硬體配置滿足要求,就可以免費升級,不需要費用。另外微軟還表示,安卓APP放到微軟的應用商店,可以用自己的支付系統,微軟也不
2021-06-29 14:45:49
豐色 發自 凹非寺量子位 報道 | 公眾號 QbitAI此前,清華大學與曠視科技曾通過結構重參數化將7年老架構VGG「升級」為效能直達SOTA的RepVGG模型。如今,這個結構重參數化系列研
2021-06-29 14:45:25