理解Python中的with…as…文法
先說明一個常見問題,檔案開啟:
try: f = open('xxx') do somethingexcept: do somethingfinally: f.close()
其實我個人不止一次在網上看到有這麼寫的了,這個是錯的。
首先正確的如下:
try: f = open('xxx')except: print 'fail to open' exit(-1)try: do somethingexcept: do somethingfinally: f.close()
很麻煩不是麼,但正確的方法就是這麼寫。
我們為什麼要寫finally,是因為防止程式拋出異常最後不能關閉檔案,但是需要關閉檔案有一個前提就是檔案已經開啟了。
在第一段錯誤碼中,如果異常發生在f=open(‘xxx’)的時候,比如檔案不存在,立馬就可以知道執行f.close()是沒有意義的。改正後的解決方案就是第二段代碼。
好了言歸正轉,開始討論with文法。
首先我們從下面這個問題談起,try-finally的文法結構:
set things uptry: do somethingfinally: tear things down
這東西是個常見結構,比如檔案開啟,set things up就表示f=open('xxx'),tear things down就表示f.close()。在比如像多線程鎖,資源請求,最終都有一個釋放的需求。Try…finally結構保證了tear things down這一段永遠都會執行,即使上面do something得工作沒有完全執行。
也就是說with是一個控制流程語句,跟if/for/while/try之類的是一類的,with可以用來簡化try finally代碼,看起來可以比try finally更清晰。
這裡新引入了一個”上下文管理協議"context management protocol",實現方法是為一個類定義__enter__和__exit__兩個函數。
with expresion as variable
的執行過程是,首先執行__enter__函數,它的傳回值會賦給as後面的variable,想讓它返回什麼就返回什麼,只要你知道怎麼處理就可以了,如果不寫as variable,傳回值會被忽略。
然後,開始執行with-block中的語句,不論成功失敗(比如發生異常、錯誤,設定sys.exit()),在with-block執行完成後,會執行__exit__函數。
這樣的過程其實等價於:
try: 執行 __enter__的內容 執行 with_block. finally: 執行 __exit__內容
最終的python-dev團隊的解決方案。(python 2.5以後增加了with運算式的文法)
class controlled_execution: def __enter__(self): set things up return thing def __exit__(self, type, value, traceback): tear things downwith controlled_execution() as thing: do something
只不過,現在把一部分代碼封裝成了__enter__函數,清理代碼封裝成__exit__函數。
我們可以自己實現一個例子:
import sys class test: def __enter__(self): print("enter") return 1 def __exit__(self,*args): print("exit") return True with test() as t: print("t is not the result of test(), it is __enter__ returned") print("t is 1, yes, it is {0}".format(t)) raise NameError("Hi there") sys.exit() print("Never here")
在這裡,python使用了with-as的文法。當python執行這一句時,會調用__enter__函數,然後把該函數return的值傳給as後指定的變數。之後,python會執行下面do something的語句塊。最後不論在該語句塊出現了什麼異常,都會在離開時執行__exit__。
另外,__exit__除了用於tear things down,還可以進行異常的監控和處理,注意後幾個參數。要跳過一個異常,只需要返回該函數True即可。下面的範例代碼跳過了所有的TypeError,而讓其他異常正常拋出。
def __exit__(self, type, value, traceback): return isinstance(value, TypeError)
在python2.5及以後,file對象已經寫好了__enter__和__exit__函數,我們可以這樣測試:
>>> f = open("x.txt")>>> f>>> f.__enter__()>>> f.read(1)'X'>>> f.__exit__(None, None, None)>>> f.read(1)Traceback (most recent call last): File "", line 1, in ValueError: I/O operation on closed file
之後,我們如果要開啟檔案並保證最後關閉他,只需要這麼做:
with open("x.txt") as f: data = f.read() do something with data
如果有多個項,我們可以這麼寫:
with open("x.txt") as f1, open('xxx.txt') as f2: do something with f1,f2
上文說了__exit__函數可以進行部分異常的處理,如果我們不在這個函數中處理異常,他會正常拋出,這時候我們可以這樣寫(python 2.7及以上版本,之前的版本參考使用contextlib.nested這個庫函數):
try: with open( "a.txt" ) as f : do somethingexcept xxxError: do something about exception
總之,with-as運算式極大的簡化了每次寫finally的工作,這對保持代碼的優雅性是有極大協助的。