一文讀懂 with……as 語句

對於文件、數據庫連接、socket 等系統資源而言,應用程序打開這些資源並執行完業務邏輯之後,必須做的一件事就是要關閉(斷開)該資源。否則會一直佔用資源,影響性能。


以向文件寫入數據為例

普通版:

f = open('file.txt', 'w')
f.write('waspvae')
f.close()

這種寫法會有一個潛在的問題,如果在調用 write 的過程中出現了異常會導致後續代碼無法執行,close 方法無法被調用,文件無法關閉,資源就會一直被該程序佔用,無法釋放。

改良版:

f = open('file.txt', 'w')
try:
f.write('waspvae')
except IOError:
print('error')
finally:
f.close()

改良版雖然解決了出現異常無法關閉的問題,但代碼有點複雜,不符合 python 精神。

終極版:

with open('file.txt', 'w') as f:
f.write('waspvae')

終極版更加優雅、簡潔。open 方法的返回值賦值給變量 f,with … as 後面的語句屬於 with 的作用域,當離開 with 作用域的時候,系統能夠自動調用 f.close() 方法,那麼它的實現原理是什麼?在講 with 的原理前要涉及到另外一個概念,就是上下文管理器(Context Manager)。

上下文管理器

任何實現了 enter() 和 exit() 方法的對象都可稱之為上下文管理器,上下文管理器對象可以使用 with 關鍵字。文件對象就是實現了上下文管理器

模擬實現一個自己的文件類,讓該方法實現 enter() 和 exit() 方法。

class File():

def __init__(self, filename, mode):
self.filename = filename
self.mode = mode

def __enter__(self):

print("entering")
self.f = open(self.filename, self.mode)
return self.f

def __exit__(self, *args):
print("will exit")
self.f.close()
# 因為 FIle 類實現了上下文管理器,所以可以使用 with 語句
with File('file.txt', 'w') as f:
print("writing")
f.write('waspvae')

打印結果為:

entering
writing
will exit

首先調用該實例的 enter 方法,打印 entering,把返回結果綁定到變量 f 上,然後打印 writing,當 with 的作用域執行完畢,調用 exit 方法,打印 will exit ,關閉文件。

實現上下管理器的另一方式(contextmanager 裝飾器)

在 contextlib 模塊中,提供了 @contextmanager 裝飾器,將一個生成器函數當成上下文管理器使用,上面的代碼與下面的代碼等效

from contextlib import contextmanager
@contextmanager

def file(filename, mode):
print("entering")
f = open(filename, mode)
yield f
print("will exit")
f.close()
with File('file.txt', 'w') as f:
print("writing")
f.write('waspvae')

通過 yield 將函數分割成兩部分,yield 之前的語句在 enter 方法中執行,yield 之後的語句在 exit 方法中執行。緊跟在 yield 後面的值是函數的返回值。


分享到:


相關文章: