Python:對于 with...as... 的理解

一個常見文件操作

打開文件:


其實我個人不止一次在網(wǎng)上看到有這么寫的了,這個是錯的。
正確的如下:

很麻煩不是么,但正確的方法就是這么寫。

  1. 我們?yōu)槭裁匆獙慺inally,是因為防止程序拋出異常最后不能關(guān)閉文件,但是需要關(guān)閉文件有一個前提就是文件已經(jīng)打開了。
  2. 在第一段錯誤代碼中,如果異常發(fā)生在f=open(‘xxx’)的時候,比如文件不存在,立馬就可以知道執(zhí)行f.close()是沒有意義的。改正后的解決方案就是第二段代碼。

好了言歸正轉(zhuǎn),開始討論with語法。

首先我們從下面這個問題談起,try-finally的語法結(jié)構(gòu):

set things up
try:
    do something
finally:
    tear things down

這東西是個常見結(jié)構(gòu),比如文件打開,

  1. set things up就表示f=open('xxx'),
  2. tear things down就表示f.close()。

解決方案1:
如果經(jīng)常用這種結(jié)構(gòu),我們首先可以采取一個較為優(yōu)雅的辦法封裝

def controlled_execution(callback):
    set things up
    try:
        callback(thing)
    finally:
        tear things down
 
def my_function(thing):
    do something
 
controlled_execution(my_function)

封裝是一個支持代碼重用的好辦法,但是這個辦法很dirty,特別是當(dāng)do something中有修改一些local variables的時候(變成函數(shù)調(diào)用,少不了帶來變量作用域上的麻煩)

解決方案2:
另一個辦法是使用生成器,但是只需要生成一次數(shù)據(jù),我們用for-in結(jié)構(gòu)去調(diào)用他

def controlled_execution():
    set things up
    try:
        yield thing
    finally:
        tear things down
         
for thing in controlled_execution():
    do something with thing

因為thing只有一個,所以yield語句只需要執(zhí)行一次。當(dāng)然,從代碼可讀性也就是優(yōu)雅的角度來說這簡直是糟糕透了。我們在確定for循環(huán)只執(zhí)行一次的情況下依然使用了for循環(huán),這代碼給不知道的人看一定很難理解這里的循環(huán)是什么個道理

解決方案3:最終版
最終的python-dev團(tuán)隊的解決方案。(python 2.5以后增加了with表達(dá)式的語法)

class controlled_execution:
    def __enter__(self):
        set things up
        return thing
    def __exit__(self, type, value, traceback):
        tear things down
         
with controlled_execution() as thing:
        do something

在這里,python使用了with-as的語法。當(dāng)python執(zhí)行這一句時,會調(diào)用enter函數(shù),然后把該函數(shù)return的值傳給as后指定的變量。
之后,python會執(zhí)行下面do something的語句塊。最后不論在該語句塊出現(xiàn)了什么異常,都會在離開時執(zhí)行exit。

另外,exit除了用于tear things down,還可以進(jìn)行異常的監(jiān)控和處理,注意后幾個參數(shù)。要跳過一個異常,只需要返回該函數(shù)True即可。下面的樣例代碼跳過了所有的TypeError,而讓其他異常正常拋出。

def __exit__(self, type, value, traceback):
    return isinstance(value, TypeError)

在python2.5及以后,file對象已經(jīng)寫好了enterexit函數(shù),我們可以這樣測試:

>>> 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

之后,我們?nèi)绻蜷_文件并保證最后關(guān)閉他,只需要這么做:

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函數(shù)可以進(jìn)行部分異常的處理,如果我們不在這個函數(shù)中處理異常,他會正常拋出,這時候我們可以這樣寫(python 2.7及以上版本,之前的版本參考使用contextlib.nested這個庫函數(shù)):

try:
    with open( "a.txt" ) as f :
        do something
except xxxError:
    do something about exception

總之,with-as表達(dá)式極大的簡化了每次寫finally的工作,這對保持代碼的優(yōu)雅性是有極大幫助的。

參考

  1. stackoverflow: Catching an exception while using a Python ‘with’ statement
  2. Understanding Python’s “with” statement
  3. http://docs.python.org/2/reference/compound_stmts.html#with
  4. http://docs.python.org/2/reference/datamodel.html#context-managers
  5. http://docs.python.org/2/library/contextlib.html#contextlib.nested
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容