Python之with語句
在Python中,我們在打開文件的時候,為了代碼的健壯性,通常要考慮一些異常情況,比如:
try: ccfile = open('/path/data') content = ccfile.readlines() ccfile.close() except IOError: log.write('no data read\n')
我們將真正干活的代碼扔到try語句塊中,如果文件操作出現異常,則寫一條錯誤日志;
考慮一種情況,如果文件打開成功,但readlines()調用失敗,異常處理會立即跳轉到except處執行,這樣文件關閉就沒有機會被執行到了。
一種解決辦法就是將close()語句放到finally子句中去,finally的特點是不管有無異常,都會被執行到。
try: try: ccfile = open('/path/data') content = ccfile.readlines() except IOError: log.write('no data read\n') finally ccfile.close()
finally的另一種可選的風格:
try: try: ccfile = open('/path/data') content = ccfile.readlines() finally IOError: ccfile.close() except IOError: log.write('no data read\n')
如上所述的標准化的 try-except和try-finally 的用法是保證資源的分配和回收,比如文件(數據、日志、數據庫等等)、線程資源、數據庫連接等,但它們書寫起來卻不夠優雅。with語句的目的在於從流程圖中把try、except、 finally關鍵字和資源分配、釋放相關代碼統統去掉,
with處理文件操作的一個實例:
with open('/etc/passwd') as f: for line in f: 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語句創建類實例了:
class A: 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__ import with_statement from contextlib import contextmanager @contextmanager def context(): print 'entering the zone' try: yield except Exception, e: print 'with an error %s'%e raise e else: print 'with no error' with context(): print '----in context call------'
參考文檔:
http://www.ibm.com/developerworks/cn/opensource/os-cn-pythonwith/