Python 中的with關(guān)鍵字使用詳解
在 Python 2.5 中, with 關(guān)鍵字被加入。它將常用的 try ... except ... finally ... 模式很方便的被復(fù)用。看一個最經(jīng)典的例子:
with open('file.txt') as f:
content = f.read()
在這段代碼中,無論 with 中的代碼塊在執(zhí)行的過程中發(fā)生任何情況,文件最終都會被關(guān)閉。如果代碼塊在執(zhí)行的過程中發(fā)生了一個異常,那么在這個異常被拋出前,程序會先將被打開的文件關(guān)閉。
再看另外一個例子。
在發(fā)起一個數(shù)據(jù)庫事務(wù)請求的時候,經(jīng)常會用類似這樣的代碼:
db.begin() try: # do some actions except: db.rollback() raise finally: db.commit()
如果將發(fā)起事務(wù)請求的操作變成可以支持 with 關(guān)鍵字的,那么用像這樣的代碼就可以了:
with transaction(db): # do some actions
下面,詳細的說明一下 with 的執(zhí)行過程,并用兩種常用的方式實現(xiàn)上面的代碼。
with 的一般執(zhí)行過程
一段基本的 with 表達式,其結(jié)構(gòu)是這樣的:
with EXPR as VAR: BLOCK
其中: EXPR 可以是任意表達式; as VAR 是可選的。其一般的執(zhí)行過程是這樣的:
- 計算 EXPR ,并獲取一個上下文管理器。
- 上下文管理器的 __exit()__ 方法被保存起來用于之后的調(diào)用。
- 調(diào)用上下文管理器的 __enter()__ 方法。
- 如果 with 表達式包含 as VAR ,那么 EXPR 的返回值被賦值給 VAR 。
- 執(zhí)行 BLOCK 中的表達式。
- 調(diào)用上下文管理器的 __exit()__ 方法。如果 BLOCK 的執(zhí)行過程中發(fā)生了一個異常導致程序退出,那么異常的 type 、 value 和 traceback (即 sys.exc_info()的返回值 )將作為參數(shù)傳遞給 __exit()__ 方法。否則,將傳遞三個 None 。
將這個過程用代碼表示,是這樣的:
mgr = (EXPR)
exit = type(mgr).__exit__ # 這里沒有執(zhí)行
value = type(mgr).__enter__(mgr)
exc = True
try:
try:
VAR = value # 如果有 as VAR
BLOCK
except:
exc = False
if not exit(mgr, *sys.exc_info()):
raise
finally:
if exc:
exit(mgr, None, None, None)
這個過程有幾個細節(jié):
如果上下文管理器中沒有 __enter()__ 或者 __exit()__ 中的任意一個方法,那么解釋器會拋出一個 AttributeError 。
在 BLOCK 中發(fā)生異常后,如果 __exit()__ 方法返回一個可被看成是 True 的值,那么這個異常就不會被拋出,后面的代碼會繼續(xù)執(zhí)行。
接下來,用兩種方法來實現(xiàn)上面來實現(xiàn)上面的過程的吧。
實現(xiàn)上下文管理器類
第一種方法是實現(xiàn)一個類,其含有一個實例屬性 db 和上下文管理器所需要的方法 __enter()__ 和 __exit()__ 。
class transaction(object):
def __init__(self, db):
self.db = db
def __enter__(self):
self.db.begin()
def __exit__(self, type, value, traceback):
if type is None:
db.commit()
else:
db.rollback()
了解 with 的執(zhí)行過程后,這個實現(xiàn)方式是很容易理解的。下面介紹的實現(xiàn)方式,其原理理解起來要復(fù)雜很多。
使用生成器裝飾器
在Python的標準庫中,有一個裝飾器可以通過生成器獲取上下文管理器。使用生成器裝飾器的實現(xiàn)過程如下:
from contextlib import contextmanager
@contextmanager
def transaction(db):
db.begin()
try:
yield db
except:
db.rollback()
raise
else:
db.commit()
第一眼上看去,這種實現(xiàn)方式更為簡單,但是其機制更為復(fù)雜??匆幌缕鋱?zhí)行過程吧:
- Python解釋器識別到 yield 關(guān)鍵字后, def 會創(chuàng)建一個生成器函數(shù)替代常規(guī)的函數(shù)(在類定義之外我喜歡用函數(shù)代替方法)。
- 裝飾器 contextmanager 被調(diào)用并返回一個幫助方法,這個幫助函數(shù)在被調(diào)用后會生成一個 GeneratorContextManager 實例。最終 with 表達式中的 EXPR 調(diào)用的是由 contentmanager 裝飾器返回的幫助函數(shù)。
- with 表達式調(diào)用 transaction(db) ,實際上是調(diào)用幫助函數(shù)。幫助函數(shù)調(diào)用生成器函數(shù),生成器函數(shù)創(chuàng)建一個生成器。
- 幫助函數(shù)將這個生成器傳遞給 GeneratorContextManager ,并創(chuàng)建一個 GeneratorContextManager 的實例對象作為上下文管理器。
- with 表達式調(diào)用實例對象的上下文管理器的 __enter()__ 方法。
- __enter()__ 方法中會調(diào)用這個生成器的 next() 方法。這時候,生成器方法會執(zhí)行到 yield db 處停止,并將 db 作為 next() 的返回值。如果有 as VAR ,那么它將會被賦值給 VAR 。
- with 中的 BLOCK 被執(zhí)行。
- BLOCK 執(zhí)行結(jié)束后,調(diào)用上下文管理器的 __exit()__ 方法。 __exit()__ 方法會再次調(diào)用生成器的 next() 方法。如果發(fā)生 StopIteration 異常,則 pass 。
- 如果沒有發(fā)生異常生成器方法將會執(zhí)行 db.commit() ,否則會執(zhí)行 db.rollback() 。
再次看看上述過程的代碼大致實現(xiàn):
def contextmanager(func):
def helper(*args, **kwargs):
return GeneratorContextManager(func(*args, **kwargs))
return helper
class GeneratorContextManager(object):
def __init__(self, gen):
self.gen = gen
def __enter__(self):
try:
return self.gen.next()
except StopIteration:
raise RuntimeError("generator didn't yield")
def __exit__(self, type, value, traceback):
if type is None:
try:
self.gen.next()
except StopIteration:
pass
else:
raise RuntimeError("generator didn't stop")
else:
try:
self.gen.throw(type, value, traceback)
raise RuntimeError("generator didn't stop after throw()")
except StopIteration:
return True
except:
if sys.exc_info()[1] is not value:
raise
總結(jié)
Python的 with 表達式包含了很多Python特性?;c時間吃透 with 是一件非常值得的事情。
一些其他的例子
鎖機制
@contextmanager
def locked(lock):
lock.acquired()
try:
yield
finally:
lock.release()
標準輸出重定向
@contextmanager
def stdout_redirect(new_stdout):
old_stdout = sys.stdout
sys.stdout = new_stdout
try:
yield
finally:
sys.stdout = old_stdout
with open("file.txt", "w") as f:
with stdout_redirect(f):
print "hello world"
參考資料
相關(guān)文章
Pytest實現(xiàn)setup和teardown的詳細使用詳解
這篇文章主要介紹了Pytest實現(xiàn)setup和teardown的詳細使用詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-04-04
Python保留數(shù)據(jù)并刪除Excel單元格的函數(shù)和公式
在分析處理Excel表格時,我們可能需要使用各種公式或函數(shù)對表格數(shù)據(jù)進行計算,從而分析出更多的信息,但在展示、分享或再利用分析結(jié)果時,我們可能需要將含有公式的單元格轉(zhuǎn)換為靜態(tài)數(shù)值,本文將介紹如何使用Python代碼批量移除Excel單元格中的公式并保留數(shù)值2024-10-10
Linux RedHat下安裝Python2.7開發(fā)環(huán)境
這篇文章主要為大家詳細介紹了Linux RedHat下安裝Python2.7、pip、ipython環(huán)境、eclipse和PyDev環(huán)境,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-05-05

