python的上下文管理器

上下文管理器的概念

上下文管理器的任務(wù)是代碼塊執(zhí)行前準(zhǔn)備杀狡,代碼塊執(zhí)行后收拾锈嫩。

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

如何使用上下文管理器

看代碼是最好的學(xué)習(xí)方式轩缤,來看看我們通常是如何打開一個(gè)文件并寫入”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行關(guān)閉文件公你。

這樣不就行了,為什么還需要上下文管理器假瞬?但是我們忽略了一個(gè)很小但是很重要的細(xì)節(jié):如果我們沒有機(jī)會到達(dá)第6行關(guān)閉文件陕靠,那會怎樣迂尝?

舉個(gè)例子,磁盤已滿剪芥,因此我們在第4行嘗試寫入文件時(shí)就會拋出異常垄开,而第6行則根本沒有機(jī)會執(zhí)行。

當(dāng)然税肪,我們可以使用try-finally語句塊來進(jìn)行包裝:

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

finally語句塊中的代碼無論try語句塊中發(fā)生了什么都會執(zhí)行溉躲。因此可以保證文件一定會關(guān)閉。這么做有什么問題么益兄?當(dāng)然沒有锻梳,但當(dāng)我們進(jìn)行一些比寫入“Hello world”更復(fù)雜的事情時(shí),try-finally語句就會變得丑陋無比净捅。例如我們要打開兩個(gè)文件疑枯,一個(gè)讀一個(gè)寫,兩個(gè)文件之間進(jìn)行拷貝操作蛔六,那么通過with語句能夠保證兩者能夠同時(shí)被關(guān)閉荆永。

OK,讓我們把事情分解一下:
首先国章,創(chuàng)建一個(gè)名為“writer”的文件變量具钥。
然后,對writer執(zhí)行一些操作液兽。
最后氓拼,關(guān)閉writer。
這樣是不是優(yōu)雅多了抵碟?

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

讓我們深入一點(diǎn)桃漾,“with”是一個(gè)新關(guān)鍵詞,并且總是伴隨著上下文管理器出現(xiàn)拟逮∏送常“open(filename, mode)”曾經(jīng)在之前的代碼中出現(xiàn)。“as”是另一個(gè)關(guān)鍵詞敦迄,它指代了從“open”函數(shù)返回的內(nèi)容恋追,并且把它賦值給了一個(gè)新的變量》N荩“writer”是一個(gè)新的變量名苦囱。

2-3行,縮進(jìn)開啟一個(gè)新的代碼塊脾猛。在這個(gè)代碼塊中撕彤,我們能夠?qū)riter做任意操作。這樣我們就使用了“open”上下文管理器,它保證我們的代碼既優(yōu)雅又安全羹铅。它出色的完成了try-finally的任務(wù)蚀狰。

open函數(shù)既能夠當(dāng)做一個(gè)簡單的函數(shù)使用,又能夠作為上下文管理器职员。這是因?yàn)閛pen函數(shù)返回了一個(gè)文件類型(file type)變量麻蹋,而這個(gè)文件類型實(shí)現(xiàn)了我們之前用到的write方法,但是想要作為上下文管理器還必須實(shí)現(xiàn)一些特殊的方法焊切,我會在接下來的小節(jié)中介紹扮授。

自定義上下文管理器

讓我們來寫一個(gè)“open”上下文管理器。
要實(shí)現(xiàn)上下文管理器专肪,必須實(shí)現(xiàn)兩個(gè)方法 –** 一個(gè)負(fù)責(zé)進(jìn)入語句塊的準(zhǔn)備操作糙箍,另一個(gè)負(fù)責(zé)離開語句塊的善后操作**。同時(shí)牵祟,我們需要兩個(gè)參數(shù):文件名和打開方式。

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

當(dāng)一個(gè)對象被用作上下文管理器時(shí):

enter 方法將在進(jìn)入代碼塊前被調(diào)用
exit 方法則在離開代碼塊之后被調(diào)用(即使在代碼塊中遇到了異常)雹拄。

下面是上下文管理器的一個(gè)例子收奔,它分別進(jìn)入和離開代碼塊時(shí)進(jìn)行打印。

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

注意一些東西:
沒有傳遞任何參數(shù)滓玖。
在此沒有使用“as”關(guān)鍵詞坪哄。
稍后我們將討論exit方法的參數(shù)設(shè)置。

我們?nèi)绾谓o一個(gè)類傳遞參數(shù)势篡?其實(shí)在任何類中翩肌,都可以使用init方法,在此我們將重寫它以接收兩個(gè)必要參數(shù)(filename, mode)禁悠。

當(dāng)我們進(jìn)入語句塊時(shí)念祭,將會使用open函數(shù),正如第一個(gè)例子中那樣碍侦。而當(dāng)我們離開語句塊時(shí)粱坤,將關(guān)閉一切在enter函數(shù)中打開的東西。

以下是我們的代碼:

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接收了兩個(gè)參數(shù)站玄。
7-9行,打開文件并返回濒旦。
12行株旷,當(dāng)離開語句塊時(shí)關(guān)閉文件。
14-15行尔邓,模仿open使用我們自己的上下文管理器灾常。

除此之外霎冯,還有一些需要強(qiáng)調(diào)的事情:
如何處理異常

我們完全忽視了語句塊內(nèi)部可能出現(xiàn)的問題。

如果語句塊內(nèi)部發(fā)生了異常钞瀑,_exit方法將被調(diào)用沈撞,而異常將會被重新拋出(re-raised)。當(dāng)處理文件寫入操作時(shí)雕什,大部分時(shí)間你肯定不希望隱藏這些異常缠俺,所以這是可以的。而對于不希望重新拋出的異常贷岸,我們可以讓 **exit**方法簡單的返回True來忽略語句塊中發(fā)生的所有異常(大部分情況下這都不是明智之舉)壹士。

我們可以在異常發(fā)生時(shí)了解到更多詳細(xì)的信息,完備的_exit_函數(shù)簽名應(yīng)該是這樣的:

def __exit__(self, exc_type, exc_val, exc_tb)

這樣_exit_函數(shù)就能夠拿到關(guān)于異常的所有信息(異常類型偿警,異常值以及異常追蹤信息),這些信息將幫助異常處理操作。在這里我將不會詳細(xì)討論異常處理該如何寫少办,以下是一個(gè)示例英妓,只負(fù)責(zé)拋出SyntaxErrors異常蔓纠。

class RaiseOnlyIfSyntaxError:

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

contextlib是一個(gè)Python模塊吗蚌,作用是提供更易用的上下文管理器。

contextlib.closing

假設(shè)我們有一個(gè)創(chuàng)建數(shù)據(jù)庫函數(shù)褪测,它將返回一個(gè)數(shù)據(jù)庫對象侮措,并且在使用完之后關(guān)閉相關(guān)資源(數(shù)據(jù)庫連接會話等)

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

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

contextlib.closing方法將在語句塊結(jié)束后調(diào)用數(shù)據(jù)庫的關(guān)閉方法澄成。

contextlib.nested

另一個(gè)很cool的特性能夠有效地幫助我們減少嵌套:
假設(shè)我們有兩個(gè)文件墨状,一個(gè)讀一個(gè)寫列赎,需要進(jìn)行拷貝。

以下是不提倡的:

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

可以通過contextlib.nested進(jìn)行簡化:

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())
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末包吝,一起剝皮案震驚了整個(gè)濱河市息堂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌持隧,老刑警劉巖屡拨,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異貌踏,居然都是意外死亡窟勃,警方通過查閱死者的電腦和手機(jī)眷昆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門纸泡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來冒黑,“玉大人田绑,你說我怎么就攤上這事÷盏” “怎么了掩驱?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長冬竟。 經(jīng)常有香客問我欧穴,道長,這世上最難降的妖魔是什么泵殴? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任涮帘,我火速辦了婚禮,結(jié)果婚禮上笑诅,老公的妹妹穿的比我還像新娘调缨。我一直安慰自己,他們只是感情好吆你,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布弦叶。 她就那樣靜靜地躺著,像睡著了一般妇多。 火紅的嫁衣襯著肌膚如雪伤哺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天者祖,我揣著相機(jī)與錄音立莉,去河邊找鬼。 笑死七问,一個(gè)胖子當(dāng)著我的面吹牛桃序,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播烂瘫,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼媒熊,長吁一口氣:“原來是場噩夢啊……” “哼奇适!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起芦鳍,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤嚷往,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后柠衅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體皮仁,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年菲宴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贷祈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡喝峦,死狀恐怖势誊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情谣蠢,我是刑警寧澤粟耻,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站眉踱,受9級特大地震影響挤忙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谈喳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一册烈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧婿禽,春花似錦赏僧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胎署。三九已至吆录,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間琼牧,已是汗流浹背恢筝。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留巨坊,地道東北人撬槽。 一個(gè)月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像趾撵,于是被迫代替她去往敵國和親侄柔。 傳聞我的和親對象是個(gè)殘疾皇子共啃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容