Python教程:with ... as 语句你懂嘛?
說到 with 大家通常看到的應(yīng)該是這樣的:
示例 1
with open('courses.txt') as f:for i in f:print(i.strip())打開一個(gè)文件,然后循環(huán)做一些事情。但是你知道為什么會(huì)有 with 嗎?我們自己是不是能夠?qū)懗隹梢宰饔迷?with 關(guān)鍵字上的對(duì)象呢?
現(xiàn)在,我們帶著上述兩個(gè)問題來說一說 with 的由來以及上下文管理器相關(guān)內(nèi)容。
with 語句的目的是簡(jiǎn)化 try/finally 模式。這種模式用于保證一段代碼運(yùn)行完畢后執(zhí)行某項(xiàng)操作,即便那段代碼由于異常、return 語句或sys.exit() 調(diào)用而中止,也會(huì)執(zhí)行指定的操作。finally 子句中的代碼通常用于釋放重要的資源,或者還原臨時(shí)變更的狀態(tài)。
示例1的功能我們可以使用 try/finally 模式實(shí)現(xiàn):
示例2
try:f = open('courses.txt')for i in f:print(i.strip()) finally:f.close()try中的 except 和 else 不是必須的,這里為了簡(jiǎn)單明了,我們只用了 finally。
對(duì)比兩個(gè)示例,我們可以看到示例1相對(duì)簡(jiǎn)潔,這就是 with 的由來。
其實(shí),語言中的一些特性或者說一些亮眼的特性,必然是有一個(gè)演化的過程的,我們作為后來者和使用者應(yīng)該多花一些心思去思索其背后的實(shí)現(xiàn)過程,相信你會(huì)收獲更多。
那么 上下文管理器 又是什么呢?
上下文管理器協(xié)議包含 __enter__ 和 __exit__ 兩個(gè)方法。with 語句開始運(yùn)行時(shí),會(huì)在上下文管理器對(duì)象上調(diào)用__enter__方法。with 語句運(yùn)行結(jié)束后,會(huì)在上下文管理器對(duì)象上調(diào)用__exit__方法,以此扮演 finally 子句的角色。最常見的例子是確保關(guān)閉文件對(duì)象(示例1)。
下面我們實(shí)現(xiàn)一個(gè)簡(jiǎn)單的例子來直觀的感受一下:
class T:def __enter__(self):print('T.__enter__')return '我是__enter__的返回值'def __exit__(self, exc_type, exc_val, exc_tb):print('T.__exit__')with T() as t:print(t)輸出:
T.__enter__ 我是__enter__的返回值 T.__exit__示例3中實(shí)現(xiàn)了一個(gè)類T,它的對(duì)象包含了__enter__和__exit__方法,有了這兩個(gè)方法就可以使用 with 處理該對(duì)象。執(zhí)行 with 后面的表達(dá)式T()得到的是上下文管理器對(duì)象,通過as字句把對(duì)象綁定到了變量t上。
觀察輸出結(jié)果,可以看到with塊先調(diào)用了__enter__方法,在處理完內(nèi)部邏輯(print(t))之后調(diào)用了exit方法,而t其實(shí)就是__enter__方法的返回值。
當(dāng)然,這個(gè)例子只是為了方便我們理解上下文管理器,下面我們看一個(gè)更有意思的例子:
示例4
obj1 = HaHa('你手機(jī)拿反了') with obj1 as content:print('哈哈鏡花緣')print(content) print('#### with 執(zhí)行完畢后,再輸出content: ####') print(content)輸出:
緣花鏡哈哈 了反拿機(jī)手你 #### with 執(zhí)行完畢后,在輸出content: #### 你手機(jī)拿反了示例4中,上下文管理器是 HaHa 類的實(shí)例,Python 調(diào)用此實(shí)例的__enter__方法,把返回結(jié)果綁定到 變量content 上。
打印一個(gè)字符串,然后打印 content 變量的值。可以看到打印出的內(nèi)容都是是反向的。
最后,當(dāng) with 塊已經(jīng)執(zhí)行完畢。可以看出,__enter__ 方法返回的值——即存儲(chǔ)在 content 變量中的值——是字符串 ‘你手機(jī)拿反了’。
輸出不再是反向的了。
HaHa類的實(shí)現(xiàn):
import sysclass HaHa:def __init__(self, word):self.word = worddef reverse_write(self, text):self.original_write(text[::-1])def __enter__(self):self.original_write = sys.stdout.writesys.stdout.write = self.reverse_writereturn self.worddef __exit__(self, exc_type, exc_value, traceback):sys.stdout.write = self.original_writereturn True在__enter__方法中,我們接管了標(biāo)準(zhǔn)輸出,將其替換成我們自己編寫的方法reverse_write,reverse_write方法將參數(shù)內(nèi)容反轉(zhuǎn)。而在__exit__方法中,我們將標(biāo)準(zhǔn)輸出還原。__exit__方法需要返回True。
總之,with之于上下文管理器,就像for之于迭代器一樣。with就是為了方便上下文管理器的使用。
上下文管理器特性在標(biāo)準(zhǔn)庫中有一些應(yīng)用:
在 sqlite3 模塊中用于管理事務(wù);在 threading 模塊中用于維護(hù)鎖、條件和信號(hào);另外,說到上下文管理器就不得不提一下@contextmanager 裝飾器,它能減少創(chuàng)建上下文管理器的樣板代碼量,因?yàn)椴挥镁帉懸粋€(gè)完整的類,定義 __enter__和 __exit__ 方法,而只需實(shí)現(xiàn)有一個(gè) yield 語句的生成器,生成想讓 __enter__ 方法返回的值。
在使用 @contextmanager 裝飾的生成器中,yield 語句的作用是把函數(shù)的定義體分成兩部分:
-
yield 語句前面的所有代碼在 with 塊開始時(shí)(即解釋器調(diào)用 __enter__ 方法時(shí))執(zhí)行
-
yield 語句后面的代碼在with 塊結(jié)束時(shí)(即調(diào)用 __exit__ 方法時(shí))執(zhí)行。
下面我們用 @contextmanager 裝飾器來實(shí)現(xiàn)一下示例4的功能:
示例5
''' 學(xué)習(xí)中遇到問題沒人解答?小編創(chuàng)建了一個(gè)Python學(xué)習(xí)交流QQ群:857662006 尋找有志同道合的小伙伴,互幫互助,群里還有不錯(cuò)的視頻學(xué)習(xí)教程和PDF電子書! ''' import sys import contextlib@contextlib.contextmanager def WoHa(n):original_write = sys.stdout.writedef reverse_write(text):original_write(text[::-1])sys.stdout.write = reverse_writeyield nsys.stdout.write = original_writereturn Trueobj1 = WoHa('你手機(jī)拿反了') with obj1 as content:print('哈哈鏡花緣')print(content) print('#### with 執(zhí)行完畢后,在輸出content: ####') print(content)輸出:
緣花鏡哈哈 了反拿機(jī)手你 #### with 執(zhí)行完畢后,在輸出content: #### 你手機(jī)拿反了這里我們需要注意的是:代碼執(zhí)行到y(tǒng)ield時(shí),會(huì)產(chǎn)出一個(gè)值,這個(gè)值會(huì)綁定到 with 語句中 as 子句的變量上。執(zhí)行 with 塊中的代碼時(shí),這個(gè)函數(shù)會(huì)在yield這里暫停。此時(shí),相當(dāng)于示例4中執(zhí)行完__enter__方法。而控制權(quán)一旦跳出 with 塊(塊內(nèi)代碼執(zhí)行完畢)則繼續(xù)執(zhí)行 yield 語句之后的代碼。
@contextmanager 裝飾器優(yōu)雅且實(shí)用,把三個(gè)不同的 Python 特性結(jié)合到了一起:函數(shù)裝飾器、生成器和 with 語句。
現(xiàn)在,我想你應(yīng)該能夠解答開篇提到的兩個(gè)問題了吧!
總結(jié)
以上是生活随笔為你收集整理的Python教程:with ... as 语句你懂嘛?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python教程:迭代器的正确使用方法
- 下一篇: Python教程:推荐一个比 open