理解Python中的with…as…文法,python
先說明一個常見問題,檔案開啟:
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<open file 'x.txt', mode 'r' at 0x00AE82F0>>>> f.__enter__()<open file 'x.txt', mode 'r' at 0x00AE82F0>>>> f.read(1)'X'>>> f.__exit__(None, None, None)>>> f.read(1)Traceback (most recent call last): File "<stdin>", line 1, in <module>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
的工作,這對保持代碼的優雅性是有極大協助的。