先說明一個常見問題,文件打開:
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的語法結構:
setthings uptry: do somethingfinally: tear things down這東西是個常見結構,比如文件打開,
set things up
就表示f=open('xxx')
,tear things down
就表示f.close()
。在比如像多線程鎖,資源請求,最終都有一個釋放的需求。Try…finally結構保證了tear things down這一段永遠都會執行,即使上面do something得工作沒有完全執行。如果經常用這種結構,我們首先可以采取一個較為優雅的辦法,封裝!
defcontrolled_execution(callback): setthings up try: callback(thing) finally: tear things down defmy_function(thing): do something controlled_execution(my_function)封裝是一個支持代碼重用的好辦法,但是這個辦法很dirty,特別是當do something中有修改一些local variables的時候(變成函數調用,少不了帶來變量作用域上的麻煩)。
另一個辦法是使用生成器,但是只需要生成一次數據,我們用for-in結構去調用他:
defcontrolled_execution(): setthings up try: yieldthing finally: tear things down forthing incontrolled_execution(): do something with thing因為thing只有一個,所以yield語句只需要執行一次。當然,從代碼可讀性也就是優雅的角度來說這簡直是糟糕透了。我們在確定for循環只執行一次的情況下依然使用了for循環,這代碼給不知道的人看一定很難理解這里的循環是什么個道理。
最終的python-dev團隊的解決方案。(python 2.5以后增加了with表達式的語法)
classcontrolled_execution: def__enter__(self): setthings up returnthing def__exit__(self,type, value, traceback): tear things down with controlled_execution() as thing: do something在這里,python使用了with-as的語法。當python執行這一句時,會調用__enter__函數,然后把該函數return的值傳給as后指定的變量。之后,python會執行下面do something的語句塊。最后不論在該語句塊出現了什么異常,都會在離開時執行__exit__。另外,__exit__除了用于tear things down,還可以進行異常的監控和處理,注意后幾個參數。要跳過一個異常,只需要返回該函數True即可。下面的樣例代碼跳過了所有的TypeError,而讓其他異常正常拋出。
def__exit__(self,type, value, traceback): returnisinstance(value, TypeError)在python2.5及以后,file對象已經寫好了__enter__和__exit__函數,我們可以這樣測試:
>>> f= open("x.txt")>>> f<openfile 'x.txt', mode 'r'at 0x00AE82F0>>>> f.__enter__()<openfile '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>", line1,in <module>ValueError: I/O Operation on closedfile之后,我們如果要打開文件并保證最后關閉他,只需要這么做:
withopen("x.txt") as f: data= f.read() do something with data如果有多個項,我們可以這么寫:
withopen("x.txt") as f1,open('xxx.txt') as f2: do something with f1,f2上文說了__exit__函數可以進行部分異常的處理,如果我們不在這個函數中處理異常,他會正常拋出,這時候我們可以這樣寫(python 2.7及以上版本,之前的版本參考使用contextlib.nested這個庫函數):
try: withopen("a.txt" ) as f : do somethingexceptxxxError: do something about exception總之,with-as表達式極大的簡化了每次寫finally的工作,這對保持代碼的優雅性是有極大幫助的。
新聞熱點
疑難解答