with與「上下文管理器」

若是你有閱讀源碼的習慣,可能會看到一些優秀的代碼常常出現帶有 「with」 關鍵字的語句,它一般用在什麼場景呢?今python

對於系統資源如文件、數據庫鏈接、socket 而言,應用程序打開這些資源並執行完業務邏輯以後,必須作的一件事就是要關閉(斷開)該資源。數據庫

好比 Python 程序打開一個文件,往文件中寫內容,寫完以後,就要關閉該文件,不然會出現什麼狀況呢?極端狀況下會出現 "Too many open files" 的錯誤,由於系統容許你打開的最大文件數量是有限的。app

一樣,對於數據庫,若是鏈接數過多而沒有及時關閉的話,就可能會出現 "Can not connect to MySQL server Too many connections",由於數據庫鏈接是一種很是昂貴的資源,不可能無限制的被建立。socket

來看看如何正確關閉一個文件。函數

普通版:

def m1(): f = open("output.txt", "w") f.write("python之禪") f.close() 

這樣寫有一個潛在的問題,若是在調用 write 的過程當中,出現了異常進而致使後續代碼沒法繼續執行,close 方法沒法被正常調用,所以資源就會一直被該程序佔用者釋放。那麼該如何改進代碼呢?oop

進階版:

def m2(): f = open("output.txt", "w") try: f.write("python之禪") except IOError: print("oops error") finally: f.close() 

改良版本的程序是對可能發生異常的代碼處進行 try 捕獲,使用 try/finally 語句,該語句表示若是在 try 代碼塊中程序出現了異常,後續代碼就再也不執行,而直接跳轉到 except 代碼塊。而不管如何,finally 塊的代碼最終都會被執行。所以,只要把 close 放在 finally 代碼中,文件就必定會關閉。spa

高級版:

def m3(): with open("output.txt", "r") as f: f.write("Python之禪") 

一種更加簡潔、優雅的方式就是用 with 關鍵字。open 方法的返回值賦值給變量 f,當離開 with 代碼塊的時候,系統會自動調用 f.close() 方法, with 的做用和使用 try/finally 語句是同樣的。那麼它的實現原理是什麼?在講 with 的原理前要涉及到另一個概念,就是上下文管理器(Context Manager)。操作系統

什麼是上下文(context)

上下文在不一樣的地方表示不一樣的含義,要感性理解。context其實說白了,和文章的上下文是一個意思,在通俗一點,我以爲叫環境更好。....code

林沖大叫一聲「啊也!」....server

問:這句話林沖的「啊也」表達了林沖怎樣的內心?

答:啊你媽個頭啊!

看,一篇文章,給你摘錄一段,沒前沒後,你讀不懂,由於有語境,就是語言環境存在,一段話說了什麼,要經過上下文(文章的上下文)來推斷。

app點擊一個按鈕進入一個新的界面,也要保存你是在哪一個屏幕跳過來的等等信息,以便你點擊返回的時候能正確跳回,若是不存確定就沒法正確跳回了。

看這些都是上下文的典型例子,理解成環境就能夠,(並且上下文雖然叫上下文,可是程序裏面通常都只有上文而已,只是叫的好聽叫上下文。。進程中斷在操做系統中是有上有下的,不過不這個高深的問題就不要深究了。。。)

上下文管理器

任何實現了 __enter__() 和 __exit__() 方法的對象均可稱之爲上下文管理器,上下文管理器對象可使用 with 關鍵字。顯然,文件(file)對象也實現了上下文管理器。

那麼文件對象是如何實現這兩個方法的呢?咱們能夠模擬實現一個本身的文件類,讓該類實現 __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() 

__enter__() 方法返回資源對象,這裏就是你將要打開的那個文件對象,__exit__() 方法處理一些清除工做。

由於 File 類實現了上下文管理器,如今就可使用 with 語句了。

with File('out.txt', 'w') as f: print("writing") f.write('hello, python') 

這樣,你就無需顯示地調用 close 方法了,由系統自動去調用,哪怕中間遇到異常 close 方法也會被調用。

實現上下文管理器的另外方式

Python 還提供了一個 contextmanager 的裝飾器,更進一步簡化了上下文管理器的實現方式。經過 yield 將函數分割成兩部分,yield 以前的語句在 __enter__ 方法中執行,yield 以後的語句在 __exit__ 方法中執行。緊跟在 yield 後面的值是函數的返回值。

from contextlib import contextmanager @contextmanager def my_open(path, mode): f = open(path, mode) yield f f.close() 

調用

with my_open('out.txt', 'w') as f:
    f.write("hello , the simplest context manager")

總結

Python 提供了 with 語法用於簡化資源操做的後續清除操做,是 try/finally 的替代方法,實現原理創建在上下文管理器之上。此外,Python 還提供了一個 contextmanager 裝飾器,更進一步簡化上下管理器的實現方式。

相關文章
相關標籤/搜索