with語句支持在一個叫上下文管理器的對象的控制下執行一系列語句,語法大概如下:
with context as var: statements
其中的context必須是個上下文管理器,它實現了兩個方法__enter__,__exit__。
在正常的管理各種系統資源(文件、鎖定和連接),在涉及到異常時通常是個棘手的問題。異常很可能導致控制流跳過負責釋放關鍵資源的語句。
看一段簡單的文件寫入代碼:
filename = 'my_file.txt' f = open(filename,'w') f.write('Hello ') f.write('World') f.close()
如果發生了意外的情況,例如寫入'World'時磁盤空間不足,就會拋出異常,那么close()語句將不會被執行。
一般的解決方案是使用try-finally語句:
try: filename = 'my_file.txt' f = open(filename,'w') f.write('Hello ') f.write('World')finally: f.close()
但隨著語句的增多,try-finally顯然不夠簡潔,用with-as語句可以很簡潔的實現以上功能:
with open('my_file','w') as f: f.write('Hello ') f.write('World')
這樣不僅能處理出現異常的情況,而且還避免了在open一個文件后忘記了寫close()方法的情況。
線程中的鎖其實也實現了上下文管理協議:
import threadingwith threading.Lock(): # 關鍵部分 statements # 關鍵部分結束
再也不怕忘記將鎖釋放了。
上下文管理器要實現__enter__和__exit__的特殊方法。
__enter__(self): 進入上下文管理器時調用此方法,其返回值將被放入with-as語句中as說明符指定的變量中。
__exit__(self,type,value,tb):離開上下文管理器調用此方法。如果有異常出現,type、value、tb分別為異常的類型、值和追蹤信息。如果沒有異常,
3個參數均設為None。此方法返回值為True或者False,分別指示被引發的異常得到了還是沒有得到處理。如果返回False,引發的異常會被傳遞出上下文。
文件上下文管理協議大概是如下實現的:
class OpenFile(object): def __init__(self,filename,mode): self.filename=filename self.mode=mode def __enter__(self): self.f=open(self.filename,self.mode) return self.f #作為as說明符指定的變量的值 def __exit__(self,type,value,tb): self.f.close() return False #異常會被傳遞出上下文with OpenFile('my_file.txt','w') as f: f.write('Hello ') f.write('World')
相當于
try : 執行__enter__的內容finally: 執行__exit__的內容
__exit__函數就能夠拿到關于異常的所有信息(異常類型,異常值以及異常追蹤信息),這些信息將幫助異常處理操作。
class ListTrans(object): def __init__(self,alist): self.alist=alist def __enter__(self): self.thecopy=list(self.alist) return self.thecopy def __exit__(self,exc_type,value,tb): if exc_type is None: self.alist[:]=self.thecopy return False
沒有異常發生時:
alist=[]with ListTrans(alist) as working: working.append(1) working.append(2)PRint alist
生成:
[1, 2]
有異常發生時:
alist=[]with ListTrans(alist) as working: working.append(1) working.append(2) raise RuntimeError('we are hosed')print alist
生成:
RuntimeError: we are hosed
alist無變化。
可以捕捉異常:
alist=[]try: with ListTrans(alist) as working: working.append(1) working.append(2) raise RuntimeError('we are hosed')except RuntimeError as e: print eprint alist
生成:
we are hosed[]
當然,也可以簡單的將__exit__的返回值設為True來忽略異常。
@contextmanager
contextlib模塊的contextmanager裝飾器可以更方便的實現上下文管理器。
任何能夠被yield關鍵詞分割成兩部分的函數,都能夠通過裝飾器裝飾的上下文管理器來實現。任何在yield之前的內容都可以看做在代碼塊執行前的操作,
而任何yield之后的操作都可以放在exit函數中。
from contextlib import contextmanager@contextmanagerdef listTrans(alist): thecopy=list(alist) yield thecopy alist[:]=thecopyalist=[]with listTrans(alist) as working: working.append(1) working.append(2)print alist
yield返回的值相當于__enter__的返回值。
要注意的是,這不是異常安全的寫法,也就是說,當出現異常時,yield后的語句是不會執行的,想要異常安全,可用try捕捉異常:
from contextlib import contextmanager@contextmanagerdef listTrans(alist): thecopy=list(alist) try: yield thecopy except RuntimeError: pass alist[:]=thecopyalist=[]with listTrans(alist) as working: working.append(1) working.append(2) raise RuntimeError
nested與closing
contextlib模塊還有兩個好玩的方法:nested,closing。
nested:用來更方便的減少嵌套寫法:
當要嵌套的寫上下文管理器時:
with open('toReadFile', 'r') as reader: with open('toWriteFile', 'w') as writer: writer.writer(reader.read())
可以用nested簡化寫法:
with contextlib.nested(open('fileToRead.txt', 'r'), open('fileToWrite.txt', 'w')) as (reader, writer): writer.write(reader.read())
python2.7后nested就過時了:
with open('fileToRead.txt', 'r') as reader,open('fileToWrite.txt', 'w') as writer: writer.write(reader.read())
closing(object):創建上下文管理器,在執行過程離開with語句時自動執行object.close():
class Door(object) : def open(self) : print 'Door is opened' def close(self) : print 'Door is closed'with contextlib.closing(Door()) as door : door.open()
新聞熱點
疑難解答