在 Python 2.5 中, with 關鍵字被加入。它將常用的 try ... except ... finally ... 模式很方便的被復用。看一個最經典的例子:
with open('file.txt') as f: content = f.read()在這段代碼中,無論 with 中的代碼塊在執行的過程中發生任何情況,文件最終都會被關閉。如果代碼塊在執行的過程中發生了一個異常,那么在這個異常被拋出前,程序會先將被打開的文件關閉。
再看另外一個例子。
在發起一個數據庫事務請求的時候,經常會用類似這樣的代碼:
db.begin()try: # do some actionsexcept: db.rollback() raisefinally: db.commit()
如果將發起事務請求的操作變成可以支持 with 關鍵字的,那么用像這樣的代碼就可以了:
with transaction(db): # do some actions
下面,詳細的說明一下 with 的執行過程,并用兩種常用的方式實現上面的代碼。
with 的一般執行過程
一段基本的 with 表達式,其結構是這樣的:
with EXPR as VAR: BLOCK
其中: EXPR 可以是任意表達式; as VAR 是可選的。其一般的執行過程是這樣的:
將這個過程用代碼表示,是這樣的:
mgr = (EXPR)exit = type(mgr).__exit__ # 這里沒有執行value = type(mgr).__enter__(mgr)exc = Truetry: try: VAR = value # 如果有 as VAR BLOCK except: exc = False if not exit(mgr, *sys.exc_info()): raisefinally: if exc: exit(mgr, None, None, None)
這個過程有幾個細節:
如果上下文管理器中沒有 __enter()__ 或者 __exit()__ 中的任意一個方法,那么解釋器會拋出一個 AttributeError 。
在 BLOCK 中發生異常后,如果 __exit()__ 方法返回一個可被看成是 True 的值,那么這個異常就不會被拋出,后面的代碼會繼續執行。
接下來,用兩種方法來實現上面來實現上面的過程的吧。
實現上下文管理器類
第一種方法是實現一個類,其含有一個實例屬性 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 的執行過程后,這個實現方式是很容易理解的。下面介紹的實現方式,其原理理解起來要復雜很多。
使用生成器裝飾器
在Python的標準庫中,有一個裝飾器可以通過生成器獲取上下文管理器。使用生成器裝飾器的實現過程如下:
from contextlib import contextmanager@contextmanagerdef transaction(db): db.begin() try: yield db except: db.rollback() raise else: db.commit()
第一眼上看去,這種實現方式更為簡單,但是其機制更為復雜。看一下其執行過程吧:
再次看看上述過程的代碼大致實現:
def contextmanager(func): def helper(*args, **kwargs): return GeneratorContextManager(func(*args, **kwargs)) return helperclass 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總結
Python的 with 表達式包含了很多Python特性。花點時間吃透 with 是一件非常值得的事情。
一些其他的例子
鎖機制
@contextmanagerdef locked(lock): lock.acquired() try: yield finally: lock.release()
標準輸出重定向
@contextmanagerdef stdout_redirect(new_stdout): old_stdout = sys.stdout sys.stdout = new_stdout try: yield finally: sys.stdout = old_stdoutwith open("file.txt", "w") as f: with stdout_redirect(f): print "hello world"參考資料
新聞熱點
疑難解答
圖片精選