with 語句是從 Python 2.5 開始引入的一種與異常處理相關的功能(2.5 版本中要經過 from __future__ import with_statement 導入後纔可使用),從 2.6 版本開始缺省可用。with 語句適用於對資源進行訪問的場合,確保無論使用過程當中是否發生異常都會執行必要的「清理」操做,釋放資源,好比文件使用後自動關閉、線程中鎖的自動獲取和釋放等。html
要使用 with 語句,首先要明白上下文管理器這一律念。有了上下文管理器,with 語句才能工做。下面是一組與上下文管理器和with 語句有關的概念。python
with 語句的語法格式以下:數據庫
with context_expression [as target(s)]:
with-body
這裏 context_expression 要返回一個上下文管理器對象,該對象並不賦值給 as 子句中的 target(s) ,若是指定了 as 子句的話,會將上下文管理器的 __enter__() 方法的返回值賦值給 target(s)。target(s) 能夠是單個變量,或者由「()」括起來的元組。express
Python 對一些內建對象進行改進,加入了對上下文管理器的支持,能夠用於 with 語句中,好比能夠自動關閉文件、線程鎖的自動獲取和釋放等。假設要對一個文件進行操做,使用 with 語句能夠有以下代碼:網絡
with open(r'somefileName') as somefile: for line in somefile: print line # ...more code
這裏使用了 with 語句,無論在處理文件過程當中是否發生異常,都能保證 with 語句執行完畢後已經關閉了打開的文件句柄。若是使用傳統的 try/finally 範式,則要使用相似以下代碼:函數
somefile = open(r'somefileName') try: for line in somefile: print line # ...more code finally: somefile.close()
比較起來,使用 with 語句能夠減小編碼量。已經加入對上下文管理協議支持的還有模塊 threading、decimal 等。PEP 0343 對with語句的實現進行了描述。with語句的執行過程相似以下代碼塊:ui
context_manager = context_expression exit = type(context_manager).__exit__ value = type(context_manager).__enter__(context_manager) exc = True # True 表示正常執行,即使有異常也忽略;False 表示從新拋出異常,須要對異常進行處理 try: try: target = value # 若是使用了 as 子句 with-body # 執行 with-body except: # 執行過程當中有異常發生 exc = False # 若是 __exit__ 返回 True,則異常被忽略;若是返回 False,則從新拋出異常 # 由外層代碼對異常進行處理 if not exit(context_manager, *sys.exc_info()): raise finally: # 正常退出,或者經過 statement-body 中的 break/continue/return 語句退出 # 或者忽略異常退出 if exc: exit(context_manager, None, None, None) # 缺省返回 None,None 在布爾上下文中看作是 False
開發人員能夠自定義支持上下文管理協議的類。自定義的上下文管理器要實現上下文管理協議所須要的 __enter__() 和 __exit__() 兩個方法:編碼
下面經過一個簡單的示例來演示如何構建自定義的上下文管理器。注意,上下文管理器必須同時提供 __enter__() 和 __exit__() 方法的定義,缺乏任何一個都會致使 AttributeError;with 語句會先檢查是否提供了 __exit__() 方法,而後檢查是否認義了 __enter__() 方法。spa
假設有一個資源 DummyResource,這種資源須要在訪問前先分配,使用完後再釋放掉;分配操做能夠放到 __enter__() 方法中,釋放操做能夠放到 __exit__() 方法中。簡單起見,這裏只經過打印語句來代表當前的操做,並無實際的資源分配與釋放。線程
class DummyResource: def __init__(self, tag): self.tag = tag print 'Resource [%s]' % tag def __enter__(self): print '[Enter %s]: Allocate resource.' % self.tag return self # 能夠返回不一樣的對象 def __exit__(self, exc_type, exc_value, exc_tb): print '[Exit %s]: Free resource.' % self.tag if exc_tb is None: print '[Exit %s]: Exited without exception.' % self.tag else: print '[Exit %s]: Exited with exception raised.' % self.tag return False # 能夠省略,缺省的None也是被看作是False
__exit__() 方法中對變量 exc_tb 進行檢測,若是不爲 None,表示發生了異常,返回 False 表示須要由外部代碼邏輯對異常進行處理;注意到若是沒有發生異常,缺省的返回值爲 None,在布爾環境中也是被看作 False,可是因爲沒有異常發生,__exit__() 的三個參數都爲 None,上下文管理代碼能夠檢測這種狀況,作正常處理。
下面在 with 語句中訪問 DummyResource :
1 with DummyResource('Normal'): 2 print '[with-body] Run without exceptions.' 3 4 with DummyResource('With-Exception'): 5 print '[with-body] Run with exception.' 6 raise Exception 7 print '[with-body] Run with exception. Failed to finish statement-body!'
1 Resource [Normal] 2 [Enter Normal]: Allocate resource. 3 [with-body] Run without exceptions. 4 [Exit Normal]: Free resource. 5 [Exit Normal]: Exited without exception.
第2個 with 語句的執行結果以下:
1 Resource [With-Exception] 2 [Enter With-Exception]: Allocate resource. 3 [with-body] Run with exception. 4 [Exit With-Exception]: Free resource. 5 [Exit With-Exception]: Exited with exception raised. 6 7 Traceback (most recent call last): 8 File "G:/demo", line 20, in <module> 9 raise Exception 10 Exception
能夠自定義上下文管理器來對軟件系統中的資源進行管理,好比數據庫鏈接、共享資源的訪問控制等。Python 在線文檔 Writing Context Managers 提供了一個針對數據庫鏈接進行管理的上下文管理器的簡單範例。
contextlib 模塊提供了3個對象:裝飾器 contextmanager、函數 nested 和上下文管理器 closing。使用這些對象,能夠對已有的生成器函數或者對象進行包裝,加入對上下文管理協議的支持,避免了專門編寫上下文管理器來支持 with 語句。
contextmanager 用於對生成器函數進行裝飾,生成器函數被裝飾之後,返回的是一個上下文管理器,其 __enter__() 和 __exit__() 方法由 contextmanager 負責提供,而再也不是以前的迭代子。被裝飾的生成器函數只能產生一個值,不然會致使異常 RuntimeError;產生的值會賦值給 as 子句中的 target,若是使用了 as 子句的話。下面看一個簡單的例子。
1 from contextlib import contextmanager 2 3 @contextmanager 4 def demo(): 5 print '[Allocate resources]' 6 print 'Code before yield-statement executes in __enter__' 7 yield '*** contextmanager demo ***' 8 print 'Code after yield-statement executes in __exit__' 9 print '[Free resources]' 10 11 with demo() as value: 12 print 'Assigned Value: %s' % value
結果輸出以下:
1 [Allocate resources] 2 Code before yield-statement executes in __enter__ 3 Assigned Value: *** contextmanager demo *** 4 Code after yield-statement executes in __exit__ 5 [Free resources]
能夠看到,生成器函數中 yield 以前的語句在 __enter__() 方法中執行,yield 以後的語句在 __exit__() 中執行,而 yield 產生的值賦給了 as 子句中的 value 變量。
須要注意的是,contextmanager 只是省略了 __enter__() / __exit__() 的編寫,但並不負責實現資源的「獲取」和「清理」工做;「獲取」操做須要定義在 yield 語句以前,「清理」操做須要定義 yield 語句以後,這樣 with 語句在執行 __enter__() / __exit__() 方法時會執行這些語句以獲取/釋放資源,即生成器函數中須要實現必要的邏輯控制,包括資源訪問出現錯誤時拋出適當的異常。
nested 能夠將多個上下文管理器組織在一塊兒,避免使用嵌套 with 語句。
1 with nested(A(), B(), C()) as (X, Y, Z): 2 # with-body code here
相似於:
with A() as X: with B() as Y: with C() as Z: # with-body code here
須要注意的是,發生異常後,若是某個上下文管理器的 __exit__() 方法對異常處理返回 False,則更外層的上下文管理器不會監測到異常。
closing 的實現以下:
class closing(object): # help doc here def __init__(self, thing): self.thing = thing def __enter__(self): return self.thing def __exit__(self, *exc_info): self.thing.close()
上下文管理器會將包裝的對象賦值給 as 子句的 target 變量,同時保證打開的對象在 with-body 執行完後會關閉掉。closing 上下文管理器包裝起來的對象必須提供 close() 方法的定義,不然執行時會報 AttributeError 錯誤。
1 class ClosingDemo(object): 2 def __init__(self): 3 self.acquire() 4 def acquire(self): 5 print 'Acquire resources.' 6 def free(self): 7 print 'Clean up any resources acquired.' 8 def close(self): 9 self.free() 10 11 with closing(ClosingDemo()): 12 print 'Using resources'
結果輸出以下:
1 Acquire resources. 2 Using resources 3 Clean up any resources acquired.
closing 適用於提供了 close() 實現的對象,好比網絡鏈接、數據庫鏈接等,也能夠在自定義類時經過接口 close() 來執行所須要的資源「清理」工做。
再看個例子:
1 from contextlib import contextmanager 2 3 @contextmanager 4 def transaction(db): 5 db.begin() 6 try: 7 yield db 8 except: 9 db.rollback() 10 raise 11 else: 12 db.commit() 13
第一眼上看去,這種實現方式更爲簡單,可是其機制更爲複雜。看一下其執行過程吧:
Python解釋器識別到yield
關鍵字後,def
會建立一個生成器函數替代常規的函數(在類定義以外我喜歡用函數代替方法)。
裝飾器contextmanager
被調用並返回一個幫助方法,這個幫助函數在被調用後會生成一個GeneratorContextManager
實例。最終with
表達式中的EXPR
調用的是由contentmanager
裝飾器返回的幫助函數。
with
表達式調用transaction(db)
,其實是調用幫助函數。幫助函數調用生成器函數,生成器函數建立一個生成器。
幫助函數將這個生成器傳遞給GeneratorContextManager
,並建立一個GeneratorContextManager
的實例對象做爲上下文管理器。
with
表達式調用實例對象的上下文管理器的__enter()__
方法。
__enter()__
方法中會調用這個生成器的next()
方法。這時候,生成器方法會執行到yield db
處中止,並將db
做爲next()
的返回值。若是有as VAR
,那麼它將會被賦值給VAR
。
with
中的BLOCK
被執行。
BLOCK
執行結束後,調用上下文管理器的__exit()__
方法。__exit()__
方法會再次調用生成器的next()
方法。若是發生StopIteration
異常,則pass
。
若是沒有發生異常生成器方法將會執行db.commit()
,不然會執行db.rollback()
。
本文對 with 語句的語法和工做機理進行了介紹,並經過示例介紹瞭如何實現自定義的上下文管理器,最後介紹瞭如何使用 contextlib 模塊來簡化上下文管理器的編寫。
參考:
一、簡書:樂樂樂樂樂正
連接:https://www.jianshu.com/p/4aaa570616bf
二、IBM developerworks
鏈接:https://www.ibm.com/developerworks/cn/opensource/os-cn-pythonwith/