with語句的應用場景 編程中有很多操作都是配套使用的,這種配套的流程可以稱為計算過程,Python語言為這種計算過程專門設計了一種結構:with語句。比如文件處理就是這類計算過程的典型代表。 使用with語句前後對比 沒有使用with語句之前,我們是這樣打開一個文件的: python操作文件的流 ...
with語句的應用場景
編程中有很多操作都是配套使用的,這種配套的流程可以稱為計算過程,Python語言為這種計算過程專門設計了一種結構:with語句。比如文件處理就是這類計算過程的典型代表。
使用with語句前後對比
沒有使用with語句之前,我們是這樣打開一個文件的:
try:
# 1. [進入]
f = open('a.txt', 'r', encoding="utf-8")
# 2. [執行]
print(f.read())
finally:
if f:
# 3. [退出]
f.close()
python操作文件的流程一般就是這三步:
- [進入]用只讀方式打開文件
如果文件不存在,open()函數就會拋出一個IOError的錯誤,並且給出錯誤碼和詳細的信息告訴你文件不存在 - [執行]讀取文件內容
如果文件打開成功,接下來,調用read()方法可以一次讀取文件的全部內容,Python把內容讀到記憶體,用一個str對象表示 - [退出]關閉打開的文件
文件使用完畢後必須關閉,因為文件對象會占用操作系統的資源,並且操作系統同一時間能打開的文件數量也是有限的
思考為什麼關閉文件操作一定要放在finallly語句里?
由於文件讀寫時都有可能產生IOError,一旦出錯,後面的f.close()就不會調用。所以,為了保證無論是否出錯都能正確地關閉文件,我們可以使用try ... finally來實現。
發現共性:
我們發現其實這種過程化的語句有共性,比如說在進去一個片段前
必須做某種超贊,處理工作後
又需要執行一個結束操作。比如上面的這段代碼:
finally:
if f:
f.close()
上面的代碼塊就可以做一個封裝。
使用with語句後,我們是這樣打開一個文件的:
with open("a.txt", "r", encoding="utf-8") as f:
print(f.read())
這個with語句和前面的try ... finally結構是一樣的,但是代碼更佳簡潔,並且不必調用f.close()方法。
with語句的執行原理
從解釋器的角度去理解with語句執行流程。
with語句的基本形式是:
with 表達式 as 變數:
語句塊
這樣的一段代碼可以稱為一個上下文(context),在執行with語句時,解釋器會先求出表達式的值,這個值(對象)是一個上下文管理器,並且假設這個對象擁有如下類的構造方法:
def __enter__():
# 描述進入上下文的動作
pass
def __exit__():
# 描述退出上下文的動作
pass
with語句在求出這個上下文管理器對象之後,自動執行進入方法
,並將這個對象的返回值賦值於 as 之後的變數,然後執行語句塊。然後在退出上下文前,自動執行對象的退出方法
。
python系統和標準庫的一些類型定義了這對操作,可以直接用於with語句。比如文件對象就直接支持這一對操作,因此可以用在with語句的頭部。
如果你也有類似的計算過程需要抽取出來,那麼可以自定義一個類,並且包含進入、退出方法。
自定義open函數
自己實現才發現,使用裝飾器和生成器就能很好的解決這個問題,不需要用到類構造方法來實現;
import contextlib # 引入上下文管理包
@contextlib.contextmanager # 給函數引入裝飾器
def myopen(dir, mode):
print("開始")
f = open(dir, mode, encoding='utf-8')
try: # 上文
yield f
finally: # 下文
print("結束")
f.close()
with myopen("a.txt", 'r') as fobj: # 把try中的yield中的f賦值給fobj
# with會將with後面的函數中的yield賦值給fobj
for i in fobj:
print(i)
# 等待上面的迴圈結束後,才最終執行finally的代碼,所以這就是上下文管理
輸出:
開始
hello,我是a.txt的第1行文字。
結束
總結
打開文件讀寫、用pickle包完成數據的存儲、恢復的操作,都非常適合使用with語句。
pickle包的使用案例:
try:
with open("phone.pickle", "wb") as outf:
pickle.dump("13193388105", outf)
except:
print("file have errow.")
try:
with open("phone.pickle", "rb") as outf:
data = pickle.load(outf)
print(type(data))
print(data)
except:
print("file have errow.")
我總結了一下使用with語句的優點:
採用with語句的代碼更簡潔;
防止因為忘記寫f.close()而引發的錯誤;
一個對象(上下文)的操作有進入、退出過程就可以抽取出來,並做成自動化執行;
參考
《從問題到程式用Python編程和計算》