1. 程式人生 > >遠離 Python 最差實踐,避免挖坑

遠離 Python 最差實踐,避免挖坑

print 信息 you 這樣的 提示 寫法 實現 debug []

原文鏈接:http://blog.guoyb.com/2016/12/03/bad-py-style/

最近在看一些陳年老系統,其中有一些不好的代碼習慣遺留下來的坑;加上最近自己也寫了一段爛代碼導致服務器負載飆升,所以就趁此機會總結下我看到過/寫過的自認為不好的 Python 代碼習慣,時刻提醒自己遠離這些“最差實踐”,避免挖坑。

下面所舉的例子中,有一部分會造成性能問題,有一部分會導致隱藏 bug,或日後維護、重構困難,還有一部分純粹是我認為不夠 pythonic。所以大家自行甄別,取精去糟吧。

函數默認參數使用可變對象

這個例子我想大家應該在各種技術文章中見過許多遍了,也足以證明這是一個大坑。

先看錯誤示範吧:

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]

理解這其中的原因,最重要的是有兩點:
函數本身也是一個對象,默認參數綁定於這個函數對象上
append 這類方法會直接修改對象,所以下次調用此函數時,其綁定的默認參數已經不再是空list了

正確的做法如下:

def donot_use_mutable_default_param(idx=0, ids=None):
   if ids is None:
       ids = []
   ids.append(idx)
   print(idx)
   print(ids)

try…except不具體指明異常類型

雖然在 Python 中使用 try…except 不會帶來嚴重的性能問題,但是不加區分,直接捕獲所有類型異常的做法,往往會掩蓋掉其他的 bug,造成難以追查的 bug。

一般的,我覺得應該盡量少的使用 try…except,這樣可以在開發期盡早的發現問題。即使要使用 try…except,也應該盡可能的指定出要捕獲的具體異常,並在 except 語句中將異常信息記入 log,或者處理完之後,再直接raise出來。

關於dict的冗余代碼

我經常能夠看到這樣的代碼:

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

其實,完全可以使用 collections.defaultdict 這一數據結構更簡單優雅的實現這樣的功能:

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)

同樣的,下面這兩種寫法一看就是帶有濃濃的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

應該用更 pythonic 的寫法:

# 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 其實還有個第二參數,表示序號從幾開始。如果想要序號從1開始數起,可以使用 enumerate(l, 1)。

使用flag變量而不使用for…else語句

同樣,這樣的代碼也很常見:

search_list = ['Jone', 'Aric', 'Luise', 'Frank', 'Wey']
found = False
for s in search_list:
   if s.startswith('C'):
       found = True
       # do something when found
       print('Found')
       break

if not found:
   # do something when not found
   print('Not found')

其實,用 for…else 更優雅:

search_list = ['Jone', 'Aric', 'Luise', 'Frank', 'Wey']
for s in search_list:
   if s.startswith('C'):
       # do something when found
       print('Found')
       break
else:
   # do something when not found
   print('Not found')

過度使用 tuple unpacking

在 Python 中,允許對 tuple 類型進行 unpack 操作,如下所示:

# human = ('James', 180, 32)
name,height,age = human

這個特性用起來很爽,比寫 name=human[0] 之類的不知道高到哪裏去了。所以,這一特性往往被濫用,一個 human 在程序的各處通過上面的方式 unpack。

然而如果後來需要在 human 中插入一個表示性別的數據 sex,那麽對於所有的這種 unpack 都需要進行修改,即使在有些邏輯中並不會使用到性別。

# human = ('James', 180, 32)
name,height,age, _ = human
# or
# name, height, age, sex = human

有如下幾種方式解決這一問題:
老老實實寫 name=human[0] 這種代碼,在需要使用性別信息處加上 sex=human[3]
使用 dict 來表示 human
使用 namedtuple

# human = namedtuple('human', ['name', 'height', 'age', 'sex'])
h = human('James', 180, 32, 0)
# then you can use h.name, h.sex and so on everywhere.

到處都是 import *

import * 是一種懶惰的行為,它不僅會汙染當前的命名空間,並且還會使得 pyflakes 等代碼檢查工具失效。在後續查看代碼或者 debug 的過程中,往往也很難從一堆 import * 中找到一個第三方函數的來源。

可以說這種習慣是百害而無一利的。

文件操作

文件操作不要使用裸奔的f = open(‘filename’)了,使用with open(‘filename’) as f來讓context manager幫你處理異常情況下的關閉文件等亂七八糟的事情多好。

野蠻使用 class.name 判斷類型

我曾經遇見過一個 bug:為了實現某特定功能,我新寫了一個 class B(A),在 B 中重寫了 A 的若幹函數。整個實現很簡單,但是就是有一部分 A 的功能無法生效。最後追查到的原因,就是在一些邏輯代碼中,硬性的判斷了entity.class.name == ‘A’。

除非你就是想限定死繼承層級中的當前類型(也就是,屏蔽未來可能會出現的子類),否則,不要使用 class.name,而改用 isinstance 這個內建函數。畢竟,Python 把這兩個變量的名字都刻意帶上那麽多下劃線,本來就是不太想讓你用嘛。

循環內部有多層函數調用

循環內部有多層函數調用,有如下兩方面的隱患:
Python 沒有 inline 函數,所以函數調用本來就會導致一定的開銷,尤其是本身邏輯簡單的時候,這個開銷所占的比例就會挺可觀的。
更嚴重的是,在之後維護這份代碼時,會容易讓人忽略掉函數是在循環中被調用的,所以容易在函數內部添加了一些開銷較大卻不必每次循環都調用的函數,比如 time.localtime()。如果是直接一個平鋪直敘的循環,我想大部分的程序員都應該知道把 time.localtime()寫到循環的外面,但是引入多層的函數調用之後,就不一定了哦。

所以我建議,在循環內部,如非特別復雜的邏輯,都應該直接寫在循環裏,不要進行函數調用。如果一定要包裝一層函數調用,應該在函數的命名或註釋中,提示後續的維護者,這個函數會在循環內部使用。


Python 是一門非常容易入門的語言,嚴格的縮進要求和豐富的內置數據類型,使得大部分 Python 代碼都能做到比較好的規範。但是,不嚴格要求自己,也很容易就寫出犯二的代碼。上面列出的只是很小的一部分,唯有多讀、多寫、多想,才能培養敏銳的代碼嗅覺,第一時間發現壞味道啊。歡迎大家補充~

遠離 Python 最差實踐,避免挖坑