摘要:引上下文管理器太極生兩儀,兩儀為陰陽(yáng)。而最常用的則是,即上下文管理器使用上下文管理器用之后的文件讀寫(xiě)會(huì)變成我們看到用了之后,代碼沒(méi)有了創(chuàng)建,也沒(méi)有了釋放。實(shí)現(xiàn)上下文管理器我們先感性地對(duì)進(jìn)行猜測(cè)。現(xiàn)實(shí)一個(gè)上下文管理器就是這么簡(jiǎn)單。
“Python有什么好學(xué)的”這句話可不是反問(wèn)句,而是問(wèn)句哦。
主要是煎魚(yú)覺(jué)得太多的人覺(jué)得Python的語(yǔ)法較為簡(jiǎn)單,寫(xiě)出來(lái)的代碼只要符合邏輯,不需要太多的學(xué)習(xí)即可,即可從一門其他語(yǔ)言跳來(lái)用Python寫(xiě)(當(dāng)然這樣是好事,誰(shuí)都希望入門簡(jiǎn)單)。
于是我便記錄一下,如果要學(xué)Python的話,到底有什么好學(xué)的。記錄一下Python有什么值得學(xué)的,對(duì)比其他語(yǔ)言有什么特別的地方,有什么樣的代碼寫(xiě)出來(lái)更Pythonic。一路回味,一路學(xué)習(xí)。
引上下文管理器太極生兩儀,兩儀為陰陽(yáng)。
道有陰陽(yáng),月有陰晴,人有生死,門有開(kāi)關(guān)。
你看這個(gè)門,它能開(kāi)能關(guān),就像這個(gè)對(duì)象,它能創(chuàng)建能釋放。(扯遠(yuǎn)了
編程這行,幾十年來(lái)都繞不開(kāi)內(nèi)存泄露這個(gè)問(wèn)題。內(nèi)存泄露的根本原因,就是把某個(gè)對(duì)象創(chuàng)建了,但是卻沒(méi)有去釋放它。直到程序結(jié)束前那一刻,這個(gè)未被釋放的對(duì)象還一直占著內(nèi)存,即使程序已經(jīng)不用這個(gè)對(duì)象了。泄露的量少的話還好,量大的話就直接打滿內(nèi)存,然后程序就被kill了。
聰明的程序員經(jīng)過(guò)了這十幾年的努力,創(chuàng)造出很多高級(jí)編程語(yǔ)言,這些編程語(yǔ)言已經(jīng)不再需要讓程序員過(guò)度關(guān)注內(nèi)存的問(wèn)題了。但是在編程時(shí),一些常見(jiàn)的對(duì)象釋放、流關(guān)閉還是要程序員顯式地寫(xiě)出來(lái)。
最常見(jiàn)的就是文件操作了。
常見(jiàn)的文件操作方式原始的Python文件操作方式,很簡(jiǎn)單,也很common(也很java):
def read_file_1(): f = open("file_demo.py", "r") try: print(f.read()) except Exception as e: pass f.close()
就是這么簡(jiǎn)簡(jiǎn)單單的,先open然后讀寫(xiě)再close,中間讀寫(xiě)加個(gè)異常處理。
其中close就是釋放資源了,在這里如果不close,可能:
資源不釋放,直到不可控的垃圾回收來(lái)了,甚至直到程序結(jié)束
中間對(duì)文件修改時(shí),修改的信息還沒(méi)來(lái)得及寫(xiě)入文件
整個(gè)代碼顯得不規(guī)范
因此寫(xiě)上close函數(shù)理論上已經(jīng)必須的了,可是xxx.close()這樣寫(xiě)上去,在邏輯復(fù)雜的時(shí)候讓人容易遺漏,同時(shí)也顯得不雅觀。
這時(shí),各種語(yǔ)言生態(tài)有各種解決方案。
像Java,就直接jvm+依賴注入,直接把對(duì)象的生命周期管理接管了,只留下對(duì)象的使用功能給程序員;像golang,defer一下就好。而python最常用的則是with,即上下文管理器
使用上下文管理器用with之后的文件讀寫(xiě)會(huì)變成:
def read_file_2(): with open("file_demo.py", "r") as f: print(f.read())
我們看到用了with之后,代碼沒(méi)有了open創(chuàng)建,也沒(méi)有了close釋放。而且也沒(méi)有了異常處理,這樣子我們一看到代碼,難免會(huì)懷疑它的健壯性。
為了更好地理解上下文管理器,我們先實(shí)現(xiàn)試試。
實(shí)現(xiàn)上下文管理器我們先感性地對(duì)with進(jìn)行猜測(cè)。
從調(diào)用with的形式上看,with像是一個(gè)函數(shù),包裹住了open和close:
# 大概意思而已 with = open + do + close def with(): open(xxxx) doSomething(xxxx) close(xxxx)
而Python的庫(kù)中已有的方案(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語(yǔ)法和修飾器(@)不熟悉的同學(xué)可以復(fù)習(xí)一下這些文章:生成器和修飾器。
然后我們調(diào)用這個(gè)上下文管理器試試,注意煎魚(yú)還給上下文管理器加了參數(shù)s,輸出的時(shí)候會(huì)帶上:
def test_context(): with c("123") as cc: print("in with") print(type(cc)) if __name__ == "__main__": test_context()
我們看到,start和end前都有實(shí)參s=123。
現(xiàn)實(shí)一個(gè)上下文管理器就是這么簡(jiǎn)單。
異常處理但是我們必須要注重異常處理,假如上面的上下文管理器中拋異常了怎么辦呢:
def test_context(): with c("123") as cc: print("in with") print(type(cc)) raise Exception
結(jié)果:
顯然,這樣弱雞的異常處理,煎魚(yú)時(shí)忍不了的。而且最重要的是,后面的close釋放居然沒(méi)有執(zhí)行!
我們可以在實(shí)現(xiàn)上下管理器時(shí),接入異常處理:
@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ù)輸出:
我們?cè)谏舷挛墓芾砥鞯膶?shí)現(xiàn)中加入了try-finally,保證出現(xiàn)異常的時(shí)候,上下文管理器也能執(zhí)行close。同時(shí)在調(diào)用with前也加入try結(jié)構(gòu),保證整個(gè)函數(shù)的正常運(yùn)行。
然而,加入了這些東西之后,整個(gè)函數(shù)變得復(fù)雜又難看。
因此,煎魚(yú)覺(jué)得,想要代碼好看,抽象的邏輯需要再次升華,即從函數(shù)的層面升為對(duì)象(類)的層面。
實(shí)現(xiàn)上下文管理器類其實(shí)用類實(shí)現(xiàn)上下文管理器,從邏輯理解上簡(jiǎn)單了很多,而且不需要引入那一個(gè)庫(kù):
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")
從代碼的字面意思上,我們就能感受得出來(lái),__enter__即為我們理解的open函數(shù),__exit__就是close函數(shù)。
接下來(lái),我們調(diào)用一下這個(gè)上下文管理器:
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é)果:
功能上和直接用修飾器一致,只是在實(shí)現(xiàn)的過(guò)程中,邏輯更清晰了。
異常處理回到我們?cè)瓉?lái)的話題:異常處理。
直接用修飾器實(shí)現(xiàn)的上下文管理器處理異常時(shí)可以說(shuō)是很難看了,那么我們的類選手表現(xiàn)又如何呢?
為了方便比較,煎魚(yú)把未進(jìn)行異常處理的和已進(jìn)行異常處理的一起寫(xiě)出來(lái),然后煎魚(yú)調(diào)用一個(gè)不存在的方法來(lái)拋異常:
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é)果:
結(jié)果發(fā)現(xiàn),看了半天,兩個(gè)類只有最后一句不一樣:異常處理的類中__exit__函數(shù)多一句返回,而且還是return了True。
而且這兩個(gè)類都完成了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ù)期異常信息一致:
先這樣吧
若有錯(cuò)誤之處請(qǐng)指出,更多地請(qǐng)關(guān)注造殼。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/45020.html
摘要:然后煎魚(yú)加了一個(gè)后再調(diào)用函數(shù),得到的輸出結(jié)果和加修飾器的一樣,換言之等效于因此,我們對(duì)于,可以理解是,它通過(guò)閉包的方式把新函數(shù)的引用賦值給了原來(lái)函數(shù)的引用。 Python有什么好學(xué)的這句話可不是反問(wèn)句,而是問(wèn)句哦。 主要是煎魚(yú)覺(jué)得太多的人覺(jué)得Python的語(yǔ)法較為簡(jiǎn)單,寫(xiě)出來(lái)的代碼只要符合邏輯,不需要太多的學(xué)習(xí)即可,即可從一門其他語(yǔ)言跳來(lái)用Python寫(xiě)(當(dāng)然這樣是好事,誰(shuí)都希望入門簡(jiǎn)...
摘要:為什么是斐波那契談到生成器迭代器,人們總是喜歡用斐波那契數(shù)列來(lái)舉例。那么,換句話來(lái)說(shuō),即能由推導(dǎo)式得出的數(shù)列,其實(shí)都可以用來(lái)做生成器迭代器的例子。然而,生成器和生成器類的實(shí)例都屬于迭代器。 Python有什么好學(xué)的這句話可不是反問(wèn)句,而是問(wèn)句哦。 主要是煎魚(yú)覺(jué)得太多的人覺(jué)得Python的語(yǔ)法較為簡(jiǎn)單,寫(xiě)出來(lái)的代碼只要符合邏輯,不需要太多的學(xué)習(xí)即可,即可從一門其他語(yǔ)言跳來(lái)用Python寫(xiě)...
摘要:今天我們來(lái)說(shuō)一個(gè)非常實(shí)用的例子,小菜接到組長(zhǎng)老王的一個(gè)任務(wù),安排一個(gè)新的活,這個(gè)活是這樣的老王小菜啊,你幫我寫(xiě)一個(gè)登入腳本,跑十幾條命令到服務(wù)器上,然后存一下日志。這個(gè)時(shí)候,小菜偷偷的瞄了一眼組長(zhǎng)老王,常舒一口氣,總于寫(xiě)完了。 Python學(xué)了好幾年,發(fā)現(xiàn)功力還是那樣,很多同學(xué)經(jīng)常這樣抱...
摘要:上下文管理器協(xié)議包含和兩個(gè)方法。因此必要時(shí)在上下文管理器函數(shù)中使用語(yǔ)句防范錯(cuò)誤。構(gòu)建臨時(shí)忽略指定異常的上下文管理器。這是個(gè)基類,用于定義基于類的上下文管理器。塊結(jié)束時(shí),按照后進(jìn)先出的順序調(diào)用棧中各個(gè)上下文管理器的方法。 導(dǎo)語(yǔ):本文章記錄了本人在學(xué)習(xí)Python基礎(chǔ)之控制流程篇的重點(diǎn)知識(shí)及個(gè)人心得,打算入門Python的朋友們可以來(lái)一起學(xué)習(xí)并交流。 本文重點(diǎn): 1、掌握if語(yǔ)句之外的el...
摘要:理解迭代對(duì)象迭代器生成器后端掘金本文源自作者的一篇博文,原文是,俺寫(xiě)的這篇文章是按照自己的理解做的參考翻譯。比較的是兩個(gè)對(duì)象的內(nèi)容是后端掘金黑魔法之協(xié)程異步后端掘金本文為作者原創(chuàng),轉(zhuǎn)載請(qǐng)先與作者聯(lián)系。 完全理解關(guān)鍵字with與上下文管理器 - 掘金如果你有閱讀源碼的習(xí)慣,可能會(huì)看到一些優(yōu)秀的代碼經(jīng)常出現(xiàn)帶有 with 關(guān)鍵字的語(yǔ)句,它通常用在什么場(chǎng)景呢?今天就來(lái)說(shuō)說(shuō) with 和 上下...
閱讀 1061·2021-10-11 10:59
閱讀 3611·2021-09-26 09:55
閱讀 906·2019-08-30 15:55
閱讀 2660·2019-08-30 15:44
閱讀 443·2019-08-30 14:06
閱讀 690·2019-08-30 11:26
閱讀 3349·2019-08-30 10:49
閱讀 2503·2019-08-29 12:53