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