Python上下文管理器

2021-08-22 13:17:42 字數 3586 閱讀 4681

對於系統資源如檔案、資料庫連線、socket 而言,應用程式開啟這些資源並執行完業務邏輯之後,必須做的一件事就是要關閉(斷開)該資源。

比如 python 程式開啟乙個檔案,往檔案中寫內容,寫完之後,就要關閉該檔案,否則會出現什麼情況呢?極端情況下會出現 "too many open files" 的錯誤,因為系統允許你開啟的最大檔案數量是有限的。

同樣,對於資料庫,如果連線數過多而沒有及時關閉的話,就可能會出現 "can not connect to mysql server too many connections",因為資料庫連線是一種非常昂貴的資源,不可能無限制的被建立。

來看看如何正確關閉乙個檔案。

def m1():

f = open("output.txt", "w")

f.write("python之禪")

這樣寫有乙個潛在的問題,如果在呼叫 write 的過程中,出現了異常進而導致後續**無法繼續執行,close 方法無法被正常呼叫,因此資源就會一直被該程式占用而無法被釋放。那麼該如何改進**呢?

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 **中,檔案就一定會關閉。

def m3():

with open("output.txt", "w") as f:

f.write("python之禪")

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

任何實現了 __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, exc_type, exc_val, exc_tb):

print("will exit")

self.f.close()

(1)當執行 with 語句的時候,物件的 __enter__() 方法被觸發, 它返回的值(如果有的話)會被賦值給 as 宣告的變數。對應應該就是你將要開啟的那個檔案物件。

(2)然後,with 語句塊裡面的**開始執行。對應的就是你將要開啟的那個檔案物件。

(3)最後,__exit__() 方法被觸發進行清理工作。對應的輸出是【in __exit__】。

因為 file 類實現了上下文管理器,現在就可以使用 with 語句了。

with file('out.txt', 'w') as f:

print("writing")

f.write('hello, python')

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

補充說明:

__exit__()方法的第三個引數包含了異常型別、異常值和追溯資訊(如果有的話)。 __exit__()方法能自己決定怎樣利用這個異常資訊,或者忽略它並返回乙個none值。

如果 __exit__() 返回 true ,那麼異常會被清空,就好像什麼都沒發生一樣, with 語句後面的程式繼續在正常執行。

上面的例子還不支援多個with巢狀使用,下面是乙個可以巢狀使用with語句的例子:

from socket import socket, af_inet, sock_stream

class connection:

def __init__(self, address, family=af_inet, type=sock_stream):

self.address = address

self.family = family

self.type = type

self.connections =

def __enter__(self):

sock = socket(self.family, self.type)

sock.connect(self.address)

return sock

def __exit__(self, exc_ty, exc_val, tb):

self.connections.pop().close()

conn = connection(('www.python.org', 80))

with conn as s1:

print(s1)

with conn as s2:

print(s2)

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 ******st context manager")

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

python 上下文管理器

上下文管理器允許你在有需要的時候,精確地分配和釋放資源。使用上下文管理器最廣泛的案例就是with語句了。想象下你有兩個需要結對執行的相關操作,然後還要在它們中間放置一段 上下文管理器就是專門讓你做這種事情的。舉個例子 with open some file w as opened file open...

python上下文管理器

上下文管理器是乙個包裝任意 塊的物件。上下文管理器保證進入上下文管理器時,每次 執行的一致性 當退出上下文管理器時,相關資源會被正確 這裡被正確 指的是在 exit 方法自定義 比如關閉資料庫游標 值得注意的是,上下文管理器一定能夠保證退出步驟的執行。如果進入上下文管理器,根據定義,一定會有退出步驟...

Python 上下文管理器

python中的上下文管理器是乙個包裝任意 塊的物件。它在處理資源的開啟關閉 異常的處理等方面有很好的實現方法。1.上下文管理器的語法 假設我們需要讀取乙個檔案中的資料,如下 try test file open test.txt r contents test file.read finally ...