“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祈坠,可能:
- 資源不釋放害碾,直到不可控的垃圾回收來了,甚至直到程序結(jié)束
- 中間對文件修改時赦拘,修改的信息還沒來得及寫入文件
- 整個代碼顯得不規(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()
我們看到,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é)果:
顯然今豆,這樣弱雞的異常處理嫌拣,煎魚時忍不了的。而且最重要的是呆躲,后面的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ù)輸出:
我們在上下文管理器的實(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é)果:
功能上和直接用修飾器一致托猩,只是在實(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é)果:
結(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ù)期異常信息一致:
先這樣吧
若有錯誤之處請指出奈泪,更多地請關(guān)注造殼。