With語句適用於對資源進行訪問的場合,確保不管使用過程中是否發生異常都會執行必要的「清理」操作,釋放資源,比如文件使用後關閉,線程中鎖的獲取和釋放等。
術語介紹一組與上下文管理器和with語句有關的概念。
上下文管理協議(Context Management Protocol):包含方法__enter__()和__exit__(),支持該協議的對象要實現這兩個方法。
上下文管理器(Context Manager):支持上下文管理協議的對象,定義執行with語句時要建立的運行時上下文,負責執行with語句塊上下文中的進入與退出操作。
運行時上下文(Runtime Context):由上下文管理器創建,通過上下文管理器的__enter__()和__exit__()方法實現。__enter__()方法在語句體執行之前進入運行時上下文;__exit__()在語句體執行完後從運行時上下文退出。
上下文表達式(Context Expression):with語句中關鍵字with的表達式,返回一個上下文管理器對象
語句體(With-Body):with語句下的代碼塊,在執行語句體之前會調用上下文管理器的__enter__()方法,執行完語句體之後會執行__exit__()方法。
with語句的語法格式如下:
with context_expression [as target(s)]: with-body
假設對一個文件進行操作,使用with語句,代碼如下:
with open(r'somefileName') as somefile: for line in somefile: print line # ...more code
不管在處理文件過程中是否發生異常,都能保證with語句執行完之後關閉文件句柄。如果使用傳統的try/finally範式,則代碼如下:
somefile = open(r'somefileName')try: for line in somefile: print line #...more codefinally: somefile.close()
相比較下,採用with語句可減少代碼量。
工作原理PEP 0343 對 with 語句的實現進行了描述。with 語句的執行過程類似如下代碼塊:
context_manager = context_expressionvalue = type(context_manager).__enter__(context_manager)exit = type(context_manager).__exit__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()): raisefinally: # 正常退出,或者通過 statement-body 中的 break/continue/return 語句退出 # 或者忽略異常退出 if exc: exit(context_manager, None, None, None) # 預設返回 None,None 在布爾上下文中看做是 False
執行context_expression,生成上下文管理器context_manager
調用上下文管理器的__enter__()方法;若使用了as子句,則將__enter__()方法的返回值賦值給as子句中的target(s)。target(s)是單個變量,或者是元組(必須加「()」)
執行語句體with-body
不管執行過程中是否發生異常,都會執行上下文管理器的__exit__()方法。__exit__()方法負責執行「清理」工作,如釋放資源等。若執行過程中沒有出現異常,或者語句體with-body中執行語句 break/continue/return,則以 None 作為參數調用 __exit__(None, None, None);若執行過程中出現異常,則使用 sys.exc_info 得到的異常信息為參數調用__exit__(exc_type, exc_value, exc_traceback)
執行語句體with-body出現異常,若__exit__(exc_type, exc_value, exc_traceback)返回False,則重新拋出異常,with語句之外的邏輯處理異常;若返回True,則忽略此異常,不再對異常進行處理。
自定義上下文管理器開發人員可以自定義支持上下文管理協議的類,實現上下文管理協議所需的兩個方法:__exit__()和__exit__(exc_type, exc_value, exc_traceback)。
注意 上下文管理器必須同時提供 __enter__() 和__exit__() 方法的定義,缺少任何一個都會導致AttributeError;with 語句先檢查是否定義了 __exit__() 方法,然後檢查是否定義了__enter__()方法。
下面通過一個簡單的示例來演示如何構建自定義的上下文管理器。
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
使用自定義的支持with語句的對象
with DummyResource('Normal'): print('[with-body] Run without exceptions.')
運行結果如下;
Resource[Normal][Enter Normal]: Allocate resource.[with-body] Run without exceptions.[Exit Normal]: Free resource.[Exit Normal]: Exited without exception.
正常執行時會先執行__enter__(),再執行完語句體 with-body,最後執行 __exit__()方法釋放資源。
with DummyResource('With-Exception'): print('[with-body] Run with exception.') raise Exception print('[with-body] Run with exception. Failed to finish statement-body')
運行結果如下:
Resource[With-Exception][Enter With-Exception]: Allocate resource.[with-body] Run with exception.[Exit With-Exception]: Free resource.[Exit With-Exception]: Exited with exception raised.Traceback (most recent call last): File "C:/DummyResource.py", line 23, in <module> raise ExceptionExceptionProcess finished with exit code 1
執行with-body語句發生異常,with-body語句體未執行完畢,但資源釋放了,並拋出異常由with-body之外代碼邏輯來捕獲處理。
可以自定義上下文管理器來對軟體系統中的資源進行管理,比如資料庫連接、共享資源的訪問控制等。Python 在線文檔 Writing Context Managers 提供了一個針對資料庫連接進行管理的上下文管理器的簡單範例。
其他特別感謝這篇文章淺談 Python 的 with 語句,讓我基本了解了with語句的用法。