python的上下文管理器

上下文管理器的概念

上下文管理器的任務是代碼塊執行前準備,代碼塊執行后收拾。

  1. 如何使用上下文管理器?

如何使用上下文管理器

看代碼是最好的學習方式,來看看我們通常是如何打開一個文件并寫入”Hello World”?

filename = 'my_file.txt'
mode = 'w' # Mode that allows to write to the file
writer = open(filename, mode)
writer.write('Hello ')
writer.write('World')
writer.close()

1-2行,我們指明文件名以及打開方式(寫入)。
第3行,打開文件。
4-5行寫入“Hello world”。
第6行關閉文件。

這樣不就行了,為什么還需要上下文管理器?但是我們忽略了一個很小但是很重要的細節:如果我們沒有機會到達第6行關閉文件,那會怎樣

舉個例子,磁盤已滿,因此我們在第4行嘗試寫入文件時就會拋出異常,而第6行則根本沒有機會執行。

當然,我們可以使用try-finally語句塊來進行包裝:

writer = open(filename, mode)
try:
    writer.write('Hello ')
    writer.write('World')
finally:
    writer.close()

finally語句塊中的代碼無論try語句塊中發生了什么都會執行。因此可以保證文件一定會關閉。這么做有什么問題么?當然沒有,但當我們進行一些比寫入“Hello world”更復雜的事情時,try-finally語句就會變得丑陋無比。例如我們要打開兩個文件,一個讀一個寫,兩個文件之間進行拷貝操作,那么通過with語句能夠保證兩者能夠同時被關閉。

OK,讓我們把事情分解一下:
首先,創建一個名為“writer”的文件變量。
然后,對writer執行一些操作。
最后,關閉writer。
這樣是不是優雅多了?

with open(filename, mode) as writer:
    writer.write('Hello ')
    writer.write('World')

讓我們深入一點,“with”是一個新關鍵詞,并且總是伴隨著上下文管理器出現。“open(filename, mode)”曾經在之前的代碼中出現。“as”是另一個關鍵詞,它指代了從“open”函數返回的內容,并且把它賦值給了一個新的變量。“writer”是一個新的變量名。

2-3行,縮進開啟一個新的代碼塊。在這個代碼塊中,我們能夠對writer做任意操作。這樣我們就使用了“open”上下文管理器,它保證我們的代碼既優雅又安全。它出色的完成了try-finally的任務。

open函數既能夠當做一個簡單的函數使用,又能夠作為上下文管理器。這是因為open函數返回了一個文件類型(file type)變量,而這個文件類型實現了我們之前用到的write方法,但是想要作為上下文管理器還必須實現一些特殊的方法,我會在接下來的小節中介紹

自定義上下文管理器

讓我們來寫一個“open”上下文管理器。
要實現上下文管理器,必須實現兩個方法 –** 一個負責進入語句塊的準備操作,另一個負責離開語句塊的善后操作**。同時,我們需要兩個參數:文件名和打開方式。

Python類包含兩個特殊的方法,分別名為:enter以及exit(雙下劃線作為前綴及后綴)

當一個對象被用作上下文管理器時:

enter 方法將在進入代碼塊前被調用
exit 方法則在離開代碼塊之后被調用(即使在代碼塊中遇到了異常)

下面是上下文管理器的一個例子,它分別進入和離開代碼塊時進行打印。

class PypixContextManagerDemo:
    def __enter__(self):
        print 'Entering the block'
    def __exit__(self, *unused):
        print 'Exiting the block'

with PypixContextManagerDemo():
    print 'In the block'

#Output:
#Entering the block
#In the block
#Exiting the block

注意一些東西:
沒有傳遞任何參數。
在此沒有使用“as”關鍵詞。
稍后我們將討論exit方法的參數設置。

我們如何給一個類傳遞參數?其實在任何類中,都可以使用init方法,在此我們將重寫它以接收兩個必要參數(filename, mode)。

當我們進入語句塊時,將會使用open函數,正如第一個例子中那樣。而當我們離開語句塊時,將關閉一切在enter函數中打開的東西。

以下是我們的代碼:

class PypixOpen:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode
 
    def __enter__(self):
        self.openedFile = open(self.filename, self.mode)
        return self.openedFile
 
    def __exit__(self, *unused):
        self.openedFile.close()
 
with PypixOpen(filename, mode) as writer:
    writer.write("Hello World from our new Context Manager!")

來看看有哪些變化:
3-5行,通過init接收了兩個參數。
7-9行,打開文件并返回。
12行,當離開語句塊時關閉文件。
14-15行,模仿open使用我們自己的上下文管理器。

除此之外,還有一些需要強調的事情:
如何處理異常

我們完全忽視了語句塊內部可能出現的問題。

如果語句塊內部發生了異常,_exit方法將被調用,而異常將會被重新拋出(re-raised)。當處理文件寫入操作時,大部分時間你肯定不希望隱藏這些異常,所以這是可以的。而對于不希望重新拋出的異常,我們可以讓 **exit**方法簡單的返回True來忽略語句塊中發生的所有異常(大部分情況下這都不是明智之舉)。

我們可以在異常發生時了解到更多詳細的信息,完備的_exit_函數簽名應該是這樣的:

def __exit__(self, exc_type, exc_val, exc_tb)

這樣_exit_函數就能夠拿到關于異常的所有信息(異常類型,異常值以及異常追蹤信息),這些信息將幫助異常處理操作。在這里我將不會詳細討論異常處理該如何寫,以下是一個示例,只負責拋出SyntaxErrors異常。

class RaiseOnlyIfSyntaxError:

    def __enter__(self):
        pass
 
    def __exit__(self, exc_type, exc_val, exc_tb):
        return SyntaxError != exc_type
  1. 談一些關于上下文庫(contextlib)的內容

contextlib是一個Python模塊,作用是提供更易用的上下文管理器。

contextlib.closing

假設我們有一個創建數據庫函數,它將返回一個數據庫對象,并且在使用完之后關閉相關資源(數據庫連接會話等)

我們可以像以往那樣處理或是通過上下文管理器:

with contextlib.closing(CreateDatabase()) as database:
    database.query()

contextlib.closing方法將在語句塊結束后調用數據庫的關閉方法。

contextlib.nested

另一個很cool的特性能夠有效地幫助我們減少嵌套:
假設我們有兩個文件,一個讀一個寫,需要進行拷貝。

以下是不提倡的:

with open('toReadFile', 'r') as reader:
    with open('toWriteFile', 'w') as writer:
        writer.writer(reader.read())

可以通過contextlib.nested進行簡化:

with contextlib.nested(open('fileToRead.txt', 'r'),
                       open('fileToWrite.txt', 'w')) as (reader, writer):
    writer.write(reader.read())

在Python2.7中這種寫法被一種新語法取代:

with open('fileToRead.txt', 'r') as reader, \
        open('fileToWrite.txt', 'w') as writer:
        writer.write(reader.read())
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

推薦閱讀更多精彩內容