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