Python標(biāo)準(zhǔn)模塊--ContextManager上下文管理器的具體用法
寫代碼時,我們希望把一些操作放到一個代碼塊中,這樣在代碼塊中執(zhí)行時就可以保持在某種運行狀態(tài),而當(dāng)離開該代碼塊時就執(zhí)行另一個操作,結(jié)束當(dāng)前狀態(tài);所以,簡單來說,上下文管理器的目的就是規(guī)定對象的使用范圍,如果超出范圍就采取“處理”。
這一功能是在Python2.5之后引進的,它的優(yōu)勢在于可以使得你的代碼更具可讀性,且不容易出錯。
1 模塊簡介
在數(shù)年前,Python 2.5 加入了一個非常特殊的關(guān)鍵字,就是with。with語句允許開發(fā)者創(chuàng)建上下文管理器。什么是上下文管理器?上下文管理器就是允許你可以自動地開始和結(jié)束一些事情。例如,你可能想要打開一個文件,然后寫入一些內(nèi)容,最后再關(guān)閉文件。這或許就是上下文管理器中一個最經(jīng)典的示例。事實上,當(dāng)你利用with語句打開一個文件時,Python替你自動創(chuàng)建了一個上下文管理器。
with open("test/test.txt","w") as f_obj: f_obj.write("hello")
如果你使用的是Python 2.4,你不得不以一種老的方式來完成這個任務(wù)。
f_obj = open("test/test.txt","w") f_obj.write("hello") f_obj.close()
上下文管理器背后工作的機制是使用Python的方法:__enter__和__exit__。讓我們嘗試著去創(chuàng)建我們的上下文管理器,以此來了解上下文管理器是如何工作的。
2 模塊使用
2.1 創(chuàng)建一個上下文管理器類
與其繼續(xù)使用Python打開文件這個例子,不如我們創(chuàng)建一個上下文管理器,這個上下文管理器將會創(chuàng)建一個SQLite數(shù)據(jù)庫連接,當(dāng)任務(wù)處理完畢,將會將其關(guān)閉。下面就是一個簡單的示例。
import sqlite3 class DataConn: def __init__(self,db_name): self.db_name = db_name def __enter__(self): self.conn = sqlite3.connect(self.db_name) return self.conn def __exit__(self,exc_type,exc_val,exc_tb): self.conn.close() if exc_val: raise if __name__ == "__main__": db = "test/test.db" with DataConn(db) as conn: cursor = conn.cursor()
在上述代碼中,我們創(chuàng)建了一個類,獲取到SQLite數(shù)據(jù)庫文件的路徑。__enter__方法將會自動執(zhí)行,并返回數(shù)據(jù)庫連接對象?,F(xiàn)在我們已經(jīng)獲取到數(shù)據(jù)庫連接對象,然后我們創(chuàng)建光標(biāo),向數(shù)據(jù)庫寫入數(shù)據(jù)或者對數(shù)據(jù)庫進行查詢。當(dāng)我們退出with語句的時候,它將會調(diào)用__exit__方法用于執(zhí)行和關(guān)閉這個連接。
讓我們使用其它的方法來創(chuàng)建上下文管理器。
2.2 利用contextlib創(chuàng)建一個上下文管理器
Python 2.5 不僅僅添加了with語句,它也添加了contextlib模塊。這就允許我們使用contextlib的contextmanager函數(shù)作為裝飾器,來創(chuàng)建一個上下文管理器。讓我們嘗試著用它來創(chuàng)建一個上下文管理器,用于打開和關(guān)閉文件。
from contextlib import contextmanager @contextmanager def file_open(path): try: f_obj = open(path,"w") yield f_obj except OSError: print("We had an error!") finally: print("Closing file") f_obj.close() if __name__ == "__main__": with file_open("test/test.txt") as fobj: fobj.write("Testing context managers")
在這里,我們從contextlib模塊中引入contextmanager,然后裝飾我們所定義的file_open函數(shù)。這就允許我們使用Python的with語句來調(diào)用file_open函數(shù)。在函數(shù)中,我們打開文件,然后通過yield,將其傳遞出去,最終主調(diào)函數(shù)可以使用它。
一旦with語句結(jié)束,控制就會返回給file_open函數(shù),它繼續(xù)執(zhí)行yield語句后面的代碼。這個最終會執(zhí)行finally語句--關(guān)閉文件。如果我們在打開文件時遇到了OSError錯誤,它就會被捕獲,最終finally語句依然會關(guān)閉文件句柄。
contextlib.closing(thing)
contextlib模塊提供了一些很方便的工具。第一個工具就是closing類,一旦代碼塊運行完畢,它就會將事件關(guān)閉。Python官方文檔給出了類似于以下的一個示例,
>>> from contextlib import contextmanager >>> @contextmanager ... def closing(db): ... try: ... yield db.conn() ... finally: ... db.close()
在這段代碼中,我們創(chuàng)建了一個關(guān)閉函數(shù),它被包裹在contextmanager中。這個與closing類相同。區(qū)別就是,我們可以在with語句中使用closing類本身,而非裝飾器。讓我們看如下的示例,
>>> from contextlib import closing >>> from urllib.request import urlopen >>> with closing(urlopen("http://www.google.com")) as webpage: ... for line in webpage: ... pass
在這個示例中,我們在closing類中打開一個url網(wǎng)頁。一旦我們運行完畢with語句,指向網(wǎng)頁的句柄就會關(guān)閉。
contextlib.suppress(*exceptions)
另一個工具就是在Python 3.4中加入的suppress類。這個上下文管理工具背后的理念就是它可以禁止任意數(shù)目的異常。假如我們想忽略FileNotFoundError異常。如果你書寫了如下的上下文管理器,那么它不會正常運行。
>>> with open("1.txt") as fobj: ... for line in fobj: ... print(line) ... Traceback (most recent call last): File "<stdin>", line 1, in <module> FileNotFoundError: [Errno 2] No such file or directory: '1.txt'
正如你所看到的,這個上下文管理器沒有處理這個異常,如果你想忽略這個錯誤,你可以按照如下方式來做,
>>> from contextlib import suppress >>> with suppress(FileNotFoundError): ... with open("1.txt") as fobj: ... for line in fobj: ... print(line)
在這段代碼中,我們引入suppress,然后將我們要忽略的異常傳遞給它,在這個例子中,就是FileNotFoundError。如果你想運行這段代碼,你將會注意到,文件不存在時,什么事情都沒有發(fā)生,也沒有錯誤被拋出。請注意,這個上下文管理器是可重用的,2.4章節(jié)將會具體解釋。
contextlib.redirect_stdout/redirect_stderr
contextlib模塊還有一對用于重定向標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯誤輸出的工具,分別在Python 3.4 和3.5 中加入。在這些工具被加入之前,如果你想對標(biāo)準(zhǔn)輸出重定向,你需要按照如下方式操作,
import sys path = "test/test.txt" with open(path,"w") as fobj: sys.stdout = fobj help(sum)
利用contextlib模塊,你可以按照如下方式操作,
from contextlib import redirect_stdout path = "test/test.txt" with open(path,"w") as fobj: with redirect_stdout(fobj): help(redirect_stdout)
在上面兩個例子中,我們均是將標(biāo)準(zhǔn)輸出重定向到一個文件。當(dāng)我們調(diào)用Python的help函數(shù),不是將信息輸出到標(biāo)準(zhǔn)輸出上,而是將信息保存到重定向的文件中。你也可以將標(biāo)準(zhǔn)輸出重定向到緩存或者從用接口如Tkinter或wxPython中獲取的文件控制類型上。
2.3 ExitStack
ExitStack是一個上下文管理器,允許你很容易地與其它上下文管理結(jié)合或者清除。這個咋聽起來讓人有些迷糊,我們來看一個Python官方文檔的例子,或許會讓我們更容易理解它。
>>> from contextlib import ExitStack >>> filenames = ["1.txt","2.txt"] >>> with ExitStack as stack: ... file_objects = [stack.enter_context(open(filename)) for filename in filenames]
這段代碼就是在列表中創(chuàng)建一系列的上下文管理器。ExitStack維護一個寄存器的棧。當(dāng)我們退出with語句時,文件就會關(guān)閉,棧就會按照相反的順序調(diào)用這些上下文管理器。
Python官方文檔中關(guān)于contextlib有很多示例,你可以學(xué)習(xí)到如下的技術(shù)點:
- 從__enter__方法中捕獲異常
- 支持不定數(shù)目的上下文管理器
- 替換掉try-finally
- 其它
2.4 可重用的上下文管理器
大部分你所創(chuàng)建的上下文管理器僅僅只能在with語句中使用一次,示例如下:
>>> from contextlib import contextmanager >>> @contextmanager ... def single(): ... print("Yielding") ... yield ... print("Exiting context manager") ... >>> context = single() >>> with context: ... pass ... Yielding Exiting context manager >>> with context: ... pass ... Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python3.4/contextlib.py", line 61, in __enter__ raise RuntimeError("generator didn't yield") from None RuntimeError: generator didn't yield
在這段代碼中,我們創(chuàng)建了一個上下文管理器實例,并嘗試著在Python的with語句中運行兩次。當(dāng)?shù)诙芜\行時,它拋出了RuntimeError。
但是如果我們想運行上下文管理器兩次呢?我們需要使用可重用的上下文管理器。讓我們使用之前所用過的redirect_stdout這個上下文管理器作為示例,
>>> from contextlib import redirect_stdout >>> from io import StringIO >>> stream = StringIO() >>> write_to_stream = redirect_stdout(stream) >>> with write_to_stream: ... print("Write something to the stream") ... with write_to_stream: ... print("Write something else to stream") ... >>> print(stream.getvalue()) Write something to the stream Write something else to stream
在這段代碼中,我們創(chuàng)建了一個上下文管理器,它們均向StringIO(一種內(nèi)存中的文件流)寫入數(shù)據(jù)。這段代碼正常運行,而沒有像之前那樣拋出RuntimeError錯誤,原因就是redirect_stdout是可重用的,允許我們可以調(diào)用兩次。當(dāng)然,實際的例子將會有更多的函數(shù)調(diào)用,會更加的復(fù)雜。一定要注意,可重用的上下文管理器不一定是線程安全的。如果你需要在線程中使用它,請先仔細閱讀Python的文檔。
2.5 總結(jié)
上下文管理器很有趣,也很方便。我經(jīng)常在自動測試中使用它們,例如,打開和關(guān)閉對話?,F(xiàn)在,你應(yīng)該可以使用Python內(nèi)置的工具去創(chuàng)建你的上下文管理器。你還可以繼續(xù)閱讀Python關(guān)于contextlib的文檔,那里有很多本文沒有覆蓋到的知識。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
python調(diào)用函數(shù)、類和文件操作簡單實例總結(jié)
這篇文章主要介紹了python調(diào)用函數(shù)、類和文件操作,結(jié)合簡單實例形式總結(jié)分析了Python調(diào)用函數(shù)、類和文件操作的各種常見操作技巧,需要的朋友可以參考下2019-11-11python2與python3爬蟲中g(shù)et與post對比解析
這篇文章主要介紹了python2與python3爬蟲中g(shù)et與post對比解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-09-09Python執(zhí)行遺傳編程gplearn庫使用實例探究
這篇文章主要為大家介紹了Python執(zhí)行遺傳編程gplearn庫使用實例探究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-01-01python編寫學(xué)生成績管理系統(tǒng)的邏輯結(jié)構(gòu)及功能實現(xiàn)
這篇文章主要為大家介紹了python編寫學(xué)生成績管理系統(tǒng)實現(xiàn)八個功能示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-04-04Python實現(xiàn)疫情通定時自動填寫功能(附代碼)
這篇文章主要介紹了Python實現(xiàn)疫情通定時自動填寫功能,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-05-05Python數(shù)據(jù)處理的26個Pandas實用技巧總結(jié)
這篇文章主要給大家分享一些pandas的實用技巧,共計26個,這些技巧在你做數(shù)據(jù)處理中必不可少,感興趣的小伙伴可以跟隨小編學(xué)習(xí)一下2022-02-02