《Python有什么好學(xué)的》之上下文管理器

“Python有什么好學(xué)的”這句話可不是反問句,而是問句哦格粪。

主要是煎魚覺得太多的人覺得Python的語法較為簡單溪椎,寫出來的代碼只要符合邏輯,不需要太多的學(xué)習(xí)即可誊锭,即可從一門其他語言跳來用Python寫(當(dāng)然這樣是好事表悬,誰都希望入門簡單)。

于是我便記錄一下炉旷,如果要學(xué)Python的話签孔,到底有什么好學(xué)的。記錄一下Python有什么值得學(xué)的窘行,對比其他語言有什么特別的地方饥追,有什么樣的代碼寫出來更Pythonic。一路回味罐盔,一路學(xué)習(xí)但绕。

引上下文管理器

太極生兩儀,兩儀為陰陽惶看。

道有陰陽捏顺,月有陰晴,人有生死纬黎,門有開關(guān)幅骄。

你看這個門,它能開能關(guān)本今,就像這個對象拆座,它能創(chuàng)建能釋放主巍。(扯遠(yuǎn)了

編程這行,幾十年來都繞不開內(nèi)存泄露這個問題挪凑。內(nèi)存泄露的根本原因孕索,就是把某個對象創(chuàng)建了,但是卻沒有去釋放它躏碳。直到程序結(jié)束前那一刻搞旭,這個未被釋放的對象還一直占著內(nèi)存,即使程序已經(jīng)不用這個對象了菇绵。泄露的量少的話還好肄渗,量大的話就直接打滿內(nèi)存,然后程序就被kill了脸甘。

聰明的程序員經(jīng)過了這十幾年的努力恳啥,創(chuàng)造出很多高級編程語言,這些編程語言已經(jīng)不再需要讓程序員過度關(guān)注內(nèi)存的問題了丹诀。但是在編程時,一些常見的對象釋放翁垂、流關(guān)閉還是要程序員顯式地寫出來铆遭。

最常見的就是文件操作了。

常見的文件操作方式

原始的Python文件操作方式沿猜,很簡單枚荣,也很common(也很java):

def read_file_1():
    f = open('file_demo.py', 'r')
    try:
        print(f.read())
    except Exception as e:
        pass
    f.close()

就是這么簡簡單單的,先open然后讀寫再close啼肩,中間讀寫加個異常處理橄妆。

其中close就是釋放資源了,在這里如果不close祈坠,可能:

  1. 資源不釋放害碾,直到不可控的垃圾回收來了,甚至直到程序結(jié)束
  2. 中間對文件修改時赦拘,修改的信息還沒來得及寫入文件
  3. 整個代碼顯得不規(guī)范

因此寫上close函數(shù)理論上已經(jīng)必須的了慌随,可是xxx.close()這樣寫上去,在邏輯復(fù)雜的時候讓人容易遺漏躺同,同時也顯得不雅觀阁猜。

這時,各種語言生態(tài)有各種解決方案蹋艺。

像Java剃袍,就直接jvm+依賴注入,直接把對象的生命周期管理接管了捎谨,只留下對象的使用功能給程序員民效;像golang隘击,defer一下就好。而python最常用的則是with研铆,即上下文管理器

使用上下文管理器

用with之后的文件讀寫會變成:

def read_file_2():
    with open('file_demo.py', 'r') as f:
        print(f.read())

我們看到用了with之后埋同,代碼沒有了open創(chuàng)建,也沒有了close釋放棵红。而且也沒有了異常處理凶赁,這樣子我們一看到代碼,難免會懷疑它的健壯性逆甜。

為了更好地理解上下文管理器虱肄,我們先實(shí)現(xiàn)試試。

實(shí)現(xiàn)上下文管理器

我們先感性地對with進(jìn)行猜測交煞。

從調(diào)用with的形式上看咏窿,with像是一個函數(shù),包裹住了open和close:

# 大概意思而已 with = open + do + close
def with():
    open(xxxx)
    doSomething(xxxx)
    close(xxxx)

而Python的庫中已有的方案(contextmanager)也和上面的偽代碼具有一定的相似性:

from contextlib import contextmanager

@contextmanager
def c(s):
    print(s + 'start')
    yield s
    print(s + 'end')

“打印start”相當(dāng)于open素征,而“打印end”相當(dāng)于close集嵌,yield語法和修飾器(@)不熟悉的同學(xué)可以復(fù)習(xí)一下這些文章:生成器修飾器

然后我們調(diào)用這個上下文管理器試試御毅,注意煎魚還給上下文管理器加了參數(shù)s根欧,輸出的時候會帶上:

def test_context():
    with c('123') as cc:
        print('in with')
        print(type(cc))

if __name__ == '__main__':
    test_context()
image

我們看到,start和end前都有實(shí)參s=123端蛆。

現(xiàn)實(shí)一個上下文管理器就是這么簡單凤粗。

異常處理

但是我們必須要注重異常處理,假如上面的上下文管理器中拋異常了怎么辦呢:

def test_context():
    with c('123') as cc:
        print('in with')
        print(type(cc))
        raise Exception

結(jié)果:


image

顯然今豆,這樣弱雞的異常處理嫌拣,煎魚時忍不了的。而且最重要的是呆躲,后面的close釋放居然沒有執(zhí)行异逐!

我們可以在實(shí)現(xiàn)上下管理器時,接入異常處理:

@contextmanager
def c():
    print('start')
    try:
        yield
    finally:
        print('end')
        
def test_except():
    try:
        with c() as cc:
            print('in with')
            raise Exception

    except:
        print('catch except')

調(diào)用test_except函數(shù)輸出:


image

我們在上下文管理器的實(shí)現(xiàn)中加入了try-finally歼秽,保證出現(xiàn)異常的時候应役,上下文管理器也能執(zhí)行close。同時在調(diào)用with前也加入try結(jié)構(gòu)燥筷,保證整個函數(shù)的正常運(yùn)行箩祥。

然而,加入了這些東西之后肆氓,整個函數(shù)變得復(fù)雜又難看袍祖。

因此,煎魚覺得谢揪,想要代碼好看蕉陋,抽象的邏輯需要再次升華捐凭,即從函數(shù)的層面升為對象(類)的層面。

實(shí)現(xiàn)上下文管理器類

其實(shí)用類實(shí)現(xiàn)上下文管理器凳鬓,從邏輯理解上簡單了很多茁肠,而且不需要引入那一個庫:

class ContextClass(object):
    def __init__(self, s):
        self.s = s

    def __enter__(self):
        print(self.s + 'call enter')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(self.s + 'call exit')

    def test(self):
        print(self.s + 'call test')

從代碼的字面意思上,我們就能感受得出來缩举,__enter__即為我們理解的open函數(shù)垦梆,__exit__就是close函數(shù)。

接下來仅孩,我們調(diào)用一下這個上下文管理器:

def test_context():
    with ContextClass('123') as c:
        print('in with')
        c.test()
        print(type(c))
        print(isinstance(c, ContextClass))

    print('')
    c = ContextClass('123')
    print(type(c))
    print(isinstance(c, ContextClass))

if __name__ == '__main__':
    test_context()

輸出結(jié)果:

image

功能上和直接用修飾器一致托猩,只是在實(shí)現(xiàn)的過程中,邏輯更清晰了辽慕。

異常處理

回到我們原來的話題:異常處理京腥。

直接用修飾器實(shí)現(xiàn)的上下文管理器處理異常時可以說是很難看了,那么我們的類選手表現(xiàn)又如何呢溅蛉?

為了方便比較公浪,煎魚把未進(jìn)行異常處理的和已進(jìn)行異常處理的一起寫出來,然后煎魚調(diào)用一個不存在的方法來拋異常:

class ContextClass(object):
    def __init__(self, s):
        self.s = s

    def __enter__(self):
        print(self.s + 'call enter')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(self.s + 'call exit')

class ContextExceptionClass(object):
    def __init__(self, s):
        self.s = s

    def __enter__(self):
        print(self.s + 'call enter')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(self.s + 'call exit')
        return True
        
def test_context():
    with ContextExceptionClass('123') as c:
        print('in with')
        t = c.test()
        print(type(t))

    # with ContextClass('456') as c:
        # print('in with')
        # t = c.test()
        # print(type(t))

if __name__ == '__main__':
    test_context()

輸出不一樣的結(jié)果:


image
image

結(jié)果發(fā)現(xiàn)温艇,看了半天因悲,兩個類只有最后一句不一樣:異常處理的類中__exit__函數(shù)多一句返回,而且還是return了True勺爱。

而且這兩個類都完成了open和close兩部,即使后者拋異常了讯检。

而在__exit__中加return True的意思就是不把異常拋出琐鲁。

如果想要詳細(xì)地處理異常,而不是像上面治標(biāo)不治本的隱藏異常人灼,則需要在__exit__函數(shù)中處理異常即可围段,因?yàn)樵摵瘮?shù)中有著異常的信息。

不信投放?稍微再改改:

class ContextExceptionClass(object):
    def __init__(self, s):
        self.s = s

    def __enter__(self):
        print(self.s + 'call enter')
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(self.s + 'call exit')
        print(str(exc_type) + ' ' + str(exc_val) + ' ' + str(exc_tb))
        return True

輸出與預(yù)期異常信息一致:

image

先這樣吧

若有錯誤之處請指出奈泪,更多地請關(guān)注造殼

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末灸芳,一起剝皮案震驚了整個濱河市涝桅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌烙样,老刑警劉巖冯遂,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異谒获,居然都是意外死亡蛤肌,警方通過查閱死者的電腦和手機(jī)壁却,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來裸准,“玉大人展东,你說我怎么就攤上這事〕淳悖” “怎么了盐肃?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長向胡。 經(jīng)常有香客問我恼蓬,道長,這世上最難降的妖魔是什么僵芹? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任处硬,我火速辦了婚禮,結(jié)果婚禮上拇派,老公的妹妹穿的比我還像新娘荷辕。我一直安慰自己,他們只是感情好件豌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布疮方。 她就那樣靜靜地躺著,像睡著了一般茧彤。 火紅的嫁衣襯著肌膚如雪骡显。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天曾掂,我揣著相機(jī)與錄音惫谤,去河邊找鬼。 笑死珠洗,一個胖子當(dāng)著我的面吹牛溜歪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播许蓖,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼蝴猪,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了膊爪?” 一聲冷哼從身側(cè)響起自阱,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蚁飒,沒想到半個月后动壤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡淮逻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年琼懊,在試婚紗的時候發(fā)現(xiàn)自己被綠了阁簸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡哼丈,死狀恐怖启妹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情醉旦,我是刑警寧澤饶米,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站车胡,受9級特大地震影響檬输,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜匈棘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一丧慈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧主卫,春花似錦逃默、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至瘩将,卻和暖如春吟税,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背姿现。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工乌妙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人建钥。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像虐沥,于是被迫代替她去往敵國和親熊经。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評論 2 353

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

  • 滿眼深黛勝似春欲险,布谷之聲處處聞镐依。 茶花抿嘴羞澀起,香樟伸頭窺游人天试。 精光運(yùn)竹節(jié)節(jié)須槐壳,發(fā)財樹下富貴人。 一切浮華訂四...
    阿丙11閱讀 211評論 0 0
  • paidaren閱讀 131評論 0 0
  • 夜半窗外風(fēng)怒號喜每, 歲末心頭鼓急敲务唐。 猶記豪情放新鷂雳攘, 可憐軀殘斷老腰。 臥床拜仙求神術(shù)枫笛, 冰凍斗志不枯槁吨灭。 只待天...
    半是瞎忙半是閑閱讀 349評論 3 3
  • “與你年輕時的容貌相比,我更愛你現(xiàn)在備受歲月摧殘的容顏”每每看到這句刑巧,其實(shí)都帶給我不小的感觸喧兄,每個人都是視覺動物,...
    巴拉巴拉炸閱讀 256評論 0 2
  • 金帆塑業(yè)閱讀 125評論 0 0