Python之with語句
在Python中,我們在打開文件的時候,為了代碼的健壯性,通常要考慮一些異常情況,比如:
try:
ccfile= open('/path/data')
content=ccfile.readlines()
ccfile.close()exceptIOError:
log.write('no data read\n')
我們將真正干活的代碼扔到try語句塊中,如果文件操作出現異常,則寫一條錯誤日志;
考慮一種情況,如果文件打開成功,但readlines()調用失敗,異常處理會立即跳轉到except處執行,這樣文件關閉就沒有機會被執行到了。
一種解決辦法就是將close()語句放到finally子句中去,finally的特點是不管有無異常,都會被執行到。
try:try:
ccfile= open('/path/data')
content=ccfile.readlines()exceptIOError:
log.write('no data read\n')finallyccfile.close()
finally的另一種可選的風格:
try:try:
ccfile= open('/path/data')
content=ccfile.readlines()finallyIOError:
ccfile.close()exceptIOError:
log.write('no data read\n')
如上所述的標準化的 try-except和try-finally 的用法是保證資源的分配和回收,比如文件(數據、日志、數據庫等等)、線程資源、數據庫連接等,但它們書寫起來卻不夠優雅。with語句的目的在于從流程圖中把try、except、 finally關鍵字和資源分配、釋放相關代碼統統去掉,
with處理文件操作的一個實例:
with open('/etc/passwd') as f:for line inf:print(line)
這段代碼的作用:打開一個文件,如果一切正常,把文件對象賦值給f,然后用迭代器遍歷文件中每一行,當完成時,關閉文件;而無論在這段代碼的任何地方,如果發生異常,此時文件仍會被關閉。
with看起來如此簡單,但是其背后還有一些工作要做,因為你不能對Python的任意符號使用with語句,它僅能工作于支持上下文管理協議(context management protocol)的對象。也就是說,只有內建了“上下文管理”的對象可以和with一起工作,目前支持該協議的對象有:
file
decimal.Context
thread.LockType
threading.Lock
threading.RLock
threading.Condition
threading.Semaphore
threading.BoundedSemaphore
現在來看with的語法:
with context_expr as var:
with_suite
當with語句執行時,便執行上下文表達式(context_expr)來獲得一個上下文管理器,上下文管理器的職責是提供一個上下文對象,用于在with語句塊中處理細節:
一旦獲得了上下文對象,就會調用它的__enter__()方法,將完成with語句塊執行前的所有準備工作,如果with語句后面跟了as語句,則用__enter__()方法的返回值來賦值;
當with語句塊結束時,無論是正常結束,還是由于異常,都會調用上下文對象的__exit__()方法,__exit__()方法有3個參數,如果with語句正常結束,三個參數全部都是 None;如果發生異常,三個參數的值分別等于調用sys.exc_info()函數返回的三個值:類型(異常類)、值(異常實例)和跟蹤記錄(traceback),相應的跟蹤記錄對象。
因為上下文管理器主要作用于共享資源,__enter__()和__exit__()方法基本是干的需要分配和釋放資源的低層次工作,比如:數據庫連接、鎖分配、信號量加/減、狀態管理、文件打開/關閉、異常處理等。
現在,我們可以在自定義類里面創建__enter__()和__exit__()方法,這樣就可以配合with語句創建類實例了:
classA:def __enter__(self):print '__enter__() called'
def __exit__(self, e_t, e_v, t_b):print '__exit__() called'with A() as a:print('got instance')
可以看到輸出為:
__enter__() called
got instance__exit__() called
另外python庫中還有一個模塊contextlib,使你不用構造含有__enter__, __exit__的類就可以使用with:
from __future__ importwith_statementfrom contextlib importcontextmanager
@contextmanagerdefcontext():print 'entering the zone'
try:yield
exceptException, e:print 'with an error %s'%eraiseeelse:print 'with no error'with context():print '----in context call------'
參考文檔:
http://www.ibm.com/developerworks/cn/opensource/os-cn-pythonwith/