「Python有什麼好學的」這句話可不是反問句,而是問句哦。java
主要是煎魚以爲太多的人以爲Python的語法較爲簡單,寫出來的代碼只要符合邏輯,不須要太多的學習便可,便可從一門其餘語言跳來用Python寫(固然這樣是好事,誰都但願入門簡單)。python
因而我便記錄一下,若是要學Python的話,到底有什麼好學的。記錄一下Python有什麼值得學的,對比其餘語言有什麼特別的地方,有什麼樣的代碼寫出來更Pythonic。一路回味,一路學習。程序員
太極生兩儀,兩儀爲陰陽。golang
道有陰陽,月有陰晴,人有生死,門有開關。shell
你看這個門,它能開能關,就像這個對象,它能建立能釋放。(扯遠了編程
編程這行,幾十年來都繞不開內存泄露這個問題。內存泄露的根本緣由,就是把某個對象建立了,可是卻沒有去釋放它。直到程序結束前那一刻,這個未被釋放的對象還一直佔着內存,即便程序已經不用這個對象了。泄露的量少的話還好,量大的話就直接打滿內存,而後程序就被kill了。jvm
聰明的程序員通過了這十幾年的努力,創造出不少高級編程語言,這些編程語言已經再也不須要讓程序員過分關注內存的問題了。可是在編程時,一些常見的對象釋放、流關閉仍是要程序員顯式地寫出來。編程語言
最多見的就是文件操做了。函數
原始的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,可能:
所以寫上close函數理論上已經必須的了,但是xxx.close()
這樣寫上去,在邏輯複雜的時候讓人容易遺漏,同時也顯得不雅觀。
這時,各類語言生態有各類解決方案。
像Java,就直接jvm+依賴注入,直接把對象的生命週期管理接管了,只留下對象的使用功能給程序員;像golang,defer一下就好。而python最經常使用的則是with,即上下文管理器
用with以後的文件讀寫會變成:
def read_file_2(): with open('file_demo.py', 'r') as f: print(f.read())
咱們看到用了with以後,代碼沒有了open建立,也沒有了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釋放竟然沒有執行!
咱們能夠在實現上下管理器時,接入異常處理:
@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,保證出現異常的時候,上下文管理器也能執行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()
輸出不同的結果:
結果發現,看了半天,兩個類只有最後一句不同:異常處理的類中__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
輸出與預期異常信息一致:
先這樣吧
如有錯誤之處請指出,更多地請關注造殼。