完全理解Python關鍵字"with"與上下文管理器
如果你有閱讀源碼的習慣,可能會看到一些優秀的代碼經常出現帶有 「with」 關鍵字的語句,它通常用在什麼場景呢?今天就來說說 with 和 上下文管理器。
對於系統資源如文件、資料庫連接、socket 而言,應用程序打開這些資源並執行完業務邏輯之後,必須做的一件事就是要關閉(斷開)該資源。
比如 Python 程序打開一個文件,往文件中寫內容,寫完之後,就要關閉該文件,否則會出現什麼情況呢?極端情況下會出現 "Too many open files" 的錯誤,因為系統允許你打開的最大文件數量是有限的。
同樣,對於資料庫,如果連接數過多而沒有及時關閉的話,就可能會出現 "Can not connect to MySQL server Too many connections",因為資料庫連接是一種非常昂貴的資源,不可能無限制的被創建。
來看看如何正確關閉一個文件。
普通版:
def m1():n f = open("output.txt", "w")n f.write("python之禪")n f.close()n
這樣寫有一個潛在的問題,如果在調用 write 的過程中,出現了異常進而導致後續代碼無法繼續執行,close 方法無法被正常調用,因此資源就會一直被該程序佔用而無法被釋放。那麼該如何改進代碼呢?
進階版:
def m2():n f = open("output.txt", "w")n try:n f.write("python之禪")n except IOError:n print("oops error")n finally:n f.close()n
改良版本的程序是對可能發生異常的代碼處進行 try 捕獲,使用 try/finally 語句,該語句表示如果在 try 代碼塊中程序出現了異常,後續代碼就不再執行,而直接跳轉到 except 代碼塊。而無論如何,finally 塊的代碼最終都會被執行。因此,只要把 close 放在 finally 代碼中,文件就一定會關閉。
高級版:
def m3():n with open("output.txt", "w") as f:n f.write("Python之禪")n
一種更加簡潔、優雅的方式就是用 with 關鍵字。open 方法的返回值賦值給變數 f,當離開 with 代碼塊的時候,系統會自動調用 f.close() 方法, with 的作用和使用 try/finally 語句是一樣的。那麼它的實現原理是什麼?在講 with 的原理前要涉及到另外一個概念,就是上下文管理器(Context Manager)。
上下文管理器
任何實現了 __enter__() 和 __exit__() 方法的對象都可稱之為上下文管理器,上下文管理器對象可以使用 with 關鍵字。顯然,文件(file)對象也實現了上下文管理器。
那麼文件對象是如何實現這兩個方法的呢?我們可以模擬實現一個自己的文件類,讓該類實現 __enter__() 和 __exit__() 方法。
class File():nn def __init__(self, filename, mode):n self.filename = filenamen self.mode = modenn def __enter__(self):n print("entering")n self.f = open(self.filename, self.mode)n return self.fnn def __exit__(self, *args):n print("will exit")n self.f.close()n
__enter__() 方法返回資源對象,這裡就是你將要打開的那個文件對象,__exit__() 方法處理一些清除工作。
因為 File 類實現了上下文管理器,現在就可以使用 with 語句了。
with File(out.txt, w) as f:n print("writing")n f.write(hello, python)n
這樣,你就無需顯示地調用 close 方法了,由系統自動去調用,哪怕中間遇到異常 close 方法也會被調用。
contextlib
Python 還提供了一個 contextmanager 的裝飾器,更進一步簡化了上下文管理器的實現方式。通過 yield 將函數分割成兩部分,yield 之前的語句在 __enter__ 方法中執行,yield 之後的語句在 __exit__ 方法中執行。緊跟在 yield 後面的值是函數的返回值。
from contextlib import contextmanagernn@contextmanagerndef my_open(path, mode):n f = open(path, mode)n yield fn f.close()n
調用
with my_open(out.txt, w) as f:n f.write("hello , the simplest context manager")n
總結
Python 提供了 with 語法用於簡化資源操作的後續清除操作,是 try/finally 的替代方法,實現原理建立在上下文管理器之上。此外,Python 還提供了一個 contextmanager 裝飾器,更進一步簡化上下管理器的實現方式。
推薦閱讀:
※爬蟲入門到精通-headers的詳細講解(模擬登錄知乎)
※【掃盲】五分鐘了解Python
※Python 3.6全揭秘
※[Python]因果檢驗工具
※Python 爬蟲實戰(一):使用 requests 和 BeautifulSoup