【Python】远离 Python 最差实践,避免挖坑
原文鏈接:http://blog.guoyb.com/2016/12/03/bad-py-style/
最近在看一些陳年老系統(tǒng),其中有一些不好的代碼習(xí)慣遺留下來(lái)的坑;加上最近自己也寫(xiě)了一段爛代碼導(dǎo)致服務(wù)器負(fù)載飆升,所以就趁此機(jī)會(huì)總結(jié)下我看到過(guò)/寫(xiě)過(guò)的自認(rèn)為不好的 Python 代碼習(xí)慣,時(shí)刻提醒自己遠(yuǎn)離這些“最差實(shí)踐”,避免挖坑。
下面所舉的例子中,有一部分會(huì)造成性能問(wèn)題,有一部分會(huì)導(dǎo)致隱藏 bug,或日后維護(hù)、重構(gòu)困難,還有一部分純粹是我認(rèn)為不夠 pythonic。所以大家自行甄別,取精去糟吧。
函數(shù)默認(rèn)參數(shù)使用可變對(duì)象
這個(gè)例子我想大家應(yīng)該在各種技術(shù)文章中見(jiàn)過(guò)許多遍了,也足以證明這是一個(gè)大坑。
先看錯(cuò)誤示范吧:
def use_mutable_default_param(idx=0, ids=[]):ids.append(idx)print(idx)print(ids)use_mutable_default_param(idx=1) use_mutable_default_param(idx=2)輸出: 1 [1] 2 [1, 2]理解這其中的原因,最重要的是有兩點(diǎn):
函數(shù)本身也是一個(gè)對(duì)象,默認(rèn)參數(shù)綁定于這個(gè)函數(shù)對(duì)象上
append 這類(lèi)方法會(huì)直接修改對(duì)象,所以下次調(diào)用此函數(shù)時(shí),其綁定的默認(rèn)參數(shù)已經(jīng)不再是空l(shuí)ist了
正確的做法如下:
def donot_use_mutable_default_param(idx=0, ids=None):if ids is None:ids = []ids.append(idx)print(idx)print(ids)try…except不具體指明異常類(lèi)型
雖然在 Python 中使用 try…except 不會(huì)帶來(lái)嚴(yán)重的性能問(wèn)題,但是不加區(qū)分,直接捕獲所有類(lèi)型異常的做法,往往會(huì)掩蓋掉其他的 bug,造成難以追查的 bug。
一般的,我覺(jué)得應(yīng)該盡量少的使用 try…except,這樣可以在開(kāi)發(fā)期盡早的發(fā)現(xiàn)問(wèn)題。即使要使用 try…except,也應(yīng)該盡可能的指定出要捕獲的具體異常,并在 except 語(yǔ)句中將異常信息記入 log,或者處理完之后,再直接raise出來(lái)。
關(guān)于dict的冗余代碼
我經(jīng)常能夠看到這樣的代碼:
d = {} datas = [1, 2, 3, 4, 2, 3, 4, 1, 5] for k in datas:if k not in d:d[k] = 0 d[k] += 1其實(shí),完全可以使用 collections.defaultdict 這一數(shù)據(jù)結(jié)構(gòu)更簡(jiǎn)單優(yōu)雅的實(shí)現(xiàn)這樣的功能:
default_d = defaultdict(lambda: 0) datas = [1, 2, 3, 4, 2, 3, 4, 1, 5] for k in datas:default_d[k] += 1同樣的,這樣的代碼:
# d is a dict if 'list' not in d: d['list'] = [] d['list'].append(x)完全可以用這樣一行代碼替代:
# d is a dict d.setdefault('list', []).append(x)同樣的,下面這兩種寫(xiě)法一看就是帶有濃濃的C味兒:
# d is a dict for k in d: v = d[k] # do something# l is a list for i in len(l): v = l[i] # do something應(yīng)該用更 pythonic 的寫(xiě)法:
# d is a dict for k, v in d.iteritems(): # do something pass# l is a list for i, v in enumerate(l): # do something pass另外,enumerate 其實(shí)還有個(gè)第二參數(shù),表示序號(hào)從幾開(kāi)始。如果想要序號(hào)從1開(kāi)始數(shù)起,可以使用 enumerate(l, 1)。
使用flag變量而不使用for…else語(yǔ)句
同樣,這樣的代碼也很常見(jiàn):
search_list = ['Jone', 'Aric', 'Luise', 'Frank', 'Wey'] found = False for s in search_list:if s.startswith('C'):found = True# do something when foundprint('Found')breakif not found:# do something when not foundprint('Not found')其實(shí),用 for…else 更優(yōu)雅:
search_list = ['Jone', 'Aric', 'Luise', 'Frank', 'Wey'] for s in search_list:if s.startswith('C'):# do something when foundprint('Found')break else:# do something when not foundprint('Not found')過(guò)度使用 tuple unpacking
在 Python 中,允許對(duì) tuple 類(lèi)型進(jìn)行 unpack 操作,如下所示:
# human = ('James', 180, 32) name,height,age = human這個(gè)特性用起來(lái)很爽,比寫(xiě) name=human[0] 之類(lèi)的不知道高到哪里去了。所以,這一特性往往被濫用,一個(gè) human 在程序的各處通過(guò)上面的方式 unpack。
然而如果后來(lái)需要在 human 中插入一個(gè)表示性別的數(shù)據(jù) sex,那么對(duì)于所有的這種 unpack 都需要進(jìn)行修改,即使在有些邏輯中并不會(huì)使用到性別。
# human = ('James', 180, 32) name,height,age, _ = human # or # name, height, age, sex = human有如下幾種方式解決這一問(wèn)題:
老老實(shí)實(shí)寫(xiě) name=human[0] 這種代碼,在需要使用性別信息處加上 sex=human[3]
使用 dict 來(lái)表示 human
使用 namedtuple
到處都是 import *
import * 是一種懶惰的行為,它不僅會(huì)污染當(dāng)前的命名空間,并且還會(huì)使得 pyflakes 等代碼檢查工具失效。在后續(xù)查看代碼或者 debug 的過(guò)程中,往往也很難從一堆 import * 中找到一個(gè)第三方函數(shù)的來(lái)源。
可以說(shuō)這種習(xí)慣是百害而無(wú)一利的。
文件操作
文件操作不要使用裸奔的f = open(‘filename’)了,使用with open(‘filename’) as f來(lái)讓context manager幫你處理異常情況下的關(guān)閉文件等亂七八糟的事情多好。
野蠻使用 class.name 判斷類(lèi)型
我曾經(jīng)遇見(jiàn)過(guò)一個(gè) bug:為了實(shí)現(xiàn)某特定功能,我新寫(xiě)了一個(gè) class B(A),在 B 中重寫(xiě)了 A 的若干函數(shù)。整個(gè)實(shí)現(xiàn)很簡(jiǎn)單,但是就是有一部分 A 的功能無(wú)法生效。最后追查到的原因,就是在一些邏輯代碼中,硬性的判斷了entity.__class__.__name__ == ‘A’。
除非你就是想限定死繼承層級(jí)中的當(dāng)前類(lèi)型(也就是,屏蔽未來(lái)可能會(huì)出現(xiàn)的子類(lèi)),否則,不要使用 class.__name__,而改用 isinstance 這個(gè)內(nèi)建函數(shù)。畢竟,Python 把這兩個(gè)變量的名字都刻意帶上那么多下劃線(xiàn),本來(lái)就是不太想讓你用嘛。
循環(huán)內(nèi)部有多層函數(shù)調(diào)用
循環(huán)內(nèi)部有多層函數(shù)調(diào)用,有如下兩方面的隱患:
Python 沒(méi)有 inline 函數(shù),所以函數(shù)調(diào)用本來(lái)就會(huì)導(dǎo)致一定的開(kāi)銷(xiāo),尤其是本身邏輯簡(jiǎn)單的時(shí)候,這個(gè)開(kāi)銷(xiāo)所占的比例就會(huì)挺可觀(guān)的。
更嚴(yán)重的是,在之后維護(hù)這份代碼時(shí),會(huì)容易讓人忽略掉函數(shù)是在循環(huán)中被調(diào)用的,所以容易在函數(shù)內(nèi)部添加了一些開(kāi)銷(xiāo)較大卻不必每次循環(huán)都調(diào)用的函數(shù),比如 time.localtime()。如果是直接一個(gè)平鋪直敘的循環(huán),我想大部分的程序員都應(yīng)該知道把 time.localtime()寫(xiě)到循環(huán)的外面,但是引入多層的函數(shù)調(diào)用之后,就不一定了哦。
所以我建議,在循環(huán)內(nèi)部,如非特別復(fù)雜的邏輯,都應(yīng)該直接寫(xiě)在循環(huán)里,不要進(jìn)行函數(shù)調(diào)用。如果一定要包裝一層函數(shù)調(diào)用,應(yīng)該在函數(shù)的命名或注釋中,提示后續(xù)的維護(hù)者,這個(gè)函數(shù)會(huì)在循環(huán)內(nèi)部使用。
Python 是一門(mén)非常容易入門(mén)的語(yǔ)言,嚴(yán)格的縮進(jìn)要求和豐富的內(nèi)置數(shù)據(jù)類(lèi)型,使得大部分 Python 代碼都能做到比較好的規(guī)范。但是,不嚴(yán)格要求自己,也很容易就寫(xiě)出犯二的代碼。上面列出的只是很小的一部分,唯有多讀、多寫(xiě)、多想,才能培養(yǎng)敏銳的代碼嗅覺(jué),第一時(shí)間發(fā)現(xiàn)壞味道啊。歡迎大家補(bǔ)充~
轉(zhuǎn)載于:https://www.cnblogs.com/Detector/p/8652968.html
總結(jié)
以上是生活随笔為你收集整理的【Python】远离 Python 最差实践,避免挖坑的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 2018美团笔试字符串问题
- 下一篇: ssdb php客户端接口文档