Python 程式設計/習語
Python 是一種強習語的語言:通常有一種最佳方法來完成某件事(一種程式設計習語),而不是很多方法:“不止一種方法可以做到”不是 Python 的座右銘。
本節從一些一般原則開始,然後遍歷語言,重點介紹如何在標準庫中習慣性地使用操作、資料型別和模組。
使用異常進行錯誤檢查,遵循 EAFP(請求原諒比獲得許可更容易)而不是 LBYL(三思而後行):將可能失敗的操作放在 try...except 塊中。
使用上下文管理器來管理資源,例如檔案。使用 finally 用於臨時清理,但更喜歡編寫上下文管理器來封裝它。
使用屬性,而不是 getter/setter 方法。
使用字典進行動態記錄,使用類進行靜態記錄(對於簡單類,使用collections.namedtuple):如果記錄始終具有相同的欄位,請在類中明確說明這一點;如果欄位可能有所不同(存在或不存在),請使用字典。
使用 _ 表示一次性變數,例如在返回元組時丟棄返回值,或者表示引數被忽略(當介面需要時,例如)。你可以使用 *_, **__ 丟棄傳遞給函式的位置引數或關鍵字引數:它們對應於通常的 *args, **kwargs 引數,但明確丟棄。你也可以在位置引數或命名引數(在你使用的引數之後)之外使用它們,允許你使用一些引數並丟棄任何多餘的引數。
使用隱式 True/False(真值/假值),除非需要區分假值,例如 None、0 和 [],在這種情況下使用顯式檢查,例如 is None 或 == 0。
在 try, for, while 之後使用可選的 else 子句,而不僅僅是 if。
為了獲得可讀且健壯的程式碼,只匯入模組,不要匯入名稱(如函式或類),因為這會建立一個新的(名稱)繫結,它不一定與現有繫結同步。[1] 例如,給定一個定義了函式 f 的模組 m,使用 from m import f 匯入函式意味著 m.f 和 f 可能不同,如果其中任何一個被賦值(建立一個新的繫結)。
在實踐中,這經常被忽略,尤其是在小規模程式碼中,因為更改模組後匯入是很少見的,因此這很少是一個問題,並且類和函式都從模組匯入,以便可以在沒有字首的情況下引用它們。但是,對於健壯的大規模程式碼,這是一條重要的規則,因為它會產生非常微妙的錯誤。
對於具有低型別化的健壯程式碼,可以使用重新命名匯入來縮寫長的模組名稱
import module_with_very_long_name as vl
vl.f() # easier than module_with_very_long_name.f, but still robust
請注意,從包使用 from 匯入子模組(或子包)是完全可以的
from p import sm # completely fine
sm.f()
- 交換值
b, a = a, b
- 可空值上的屬性訪問
要訪問可能是一個物件的值(尤其是呼叫方法)或可能是 None 的屬性,請使用 and 的布林短路。
a and a.x
a and a.f()
對正則表示式匹配特別有用
match and match.group(0)
- in
使用 in 進行子字串檢查。
- 迭代期間的索引
如果你需要跟蹤可迭代物件上的迭代週期,請使用 enumerate()
for i, x in enumerate(l):
# ...
反習語
for i in range(len(l)):
x = l[i] # why did you go from list to numbers back to the list?
# ...
- 查詢第一個匹配元素
Python 序列確實有一個 index 方法,但它返回序列中特定值首次出現的索引。要找到滿足條件的值的首次出現,請改用 next 和生成器表示式
try:
x = next(i for i, n in enumerate(l) if n > 0)
except StopIteration:
print('No positive numbers')
else:
print('The index of the first positive number is', x)
如果你需要值,而不是它出現的索引,你可以直接透過以下方式獲得它
try:
x = next(n for n in l if n > 0)
except StopIteration:
print('No positive numbers')
else:
print('The first positive number is', x)
這種結構的原因是雙重的
- 異常允許你發出“未找到匹配項”的訊號(它們解決了半謂詞問題):由於你正在返回單個值(而不是索引),因此無法在值中返回它。
- 生成器表示式允許你使用表示式而不必使用 lambda 或引入新語法。
- 截斷
對於可變序列,請使用 del,而不是重新分配給切片
del l[j:]
del l[:i]
反習語
l = l[:j]
l = l[i:]
最簡單的原因是 del 使你的意圖明確:你正在截斷。
更微妙的是,切片會建立對同一個列表的另一個引用(因為列表是可變的),然後無法訪問的資料可以被垃圾回收,但這通常在以後進行。刪除立即就地修改列表(這比建立切片然後將其分配給現有變數更快),並允許 Python 立即釋放已刪除的元素,而不是等待垃圾回收。
在某些情況下,你確實希望擁有同一個列表的 2 個切片——儘管這在基本程式設計中很少見,除了在 for 迴圈中迭代一次切片以外——但很少會希望對整個列表進行切片,然後用切片替換原始列表變數(但不要更改另一個切片!),就像以下看起來很奇怪的程式碼一樣
m = l
l = l[i:j] # why not m = l[i:j] ?
- 來自可迭代物件的排序列表
你可以直接從任何可迭代物件建立排序列表,而不必先建立列表然後排序。這些包括集合和字典(迭代鍵)
s = {1, 'a', ...}
l = sorted(s)
d = {'a': 1, ...}
l = sorted(d)
使用元組表示常量序列。這很少必要(主要是在用作字典中的鍵時),但這會使意圖明確。
- 子字串
使用 in 進行子字串檢查。
但是,不要使用 in 檢查字串是否為單字元匹配,因為它會匹配子字串並返回虛假匹配——請改用有效值的元組。例如,以下錯誤
def valid_sign(sign):
return sign in '+-' # wrong, returns true for sign == '+-'
相反,請使用元組
def valid_sign(sign):
return sign in ('+', '-')
- 構建字串
要逐步建立一個長字串,請構建一個列表,然後使用 '' 連線它——或者使用換行符,如果要構建一個文字檔案(在這種情況下,不要忘記最後的換行符!)。這比追加到字串更快更清晰,這通常很慢。(原則上可以是 在字串的總長度和新增次數上,如果部分大小相似,則為。)
但是,某些版本的 CPython 中有一些最佳化可以使簡單的字串追加變得更快——CPython 2.5+ 中的字串追加以及 CPython 3.0+ 中的位元組串追加速度很快,但對於構建 Unicode 字串(Python 2 中的 unicode,Python 3 中的 string),連線速度更快。如果進行大量的字串操作,請注意這一點並分析你的程式碼。有關詳細資訊,請參閱效能提示:字串連線和連線測試程式碼。
不要這樣做
s = ''
for x in l:
# this makes a new string every iteration, because strings are immutable
s += x
相反
# ...
# l.append(x)
s = ''.join(l)
你甚至可以使用生成器表示式,它們非常高效
s = ''.join(f(x) for x in l)
如果你確實需要一個可變的類似字串的物件,可以使用 StringIO。
- Python 中的高效字串連線——舊文章(因此基準測試已過時),但提供了對一些技術的概述。
迭代字典時,可以遍歷鍵、值或兩者
# Iterate over keys
for k in d:
...
# Iterate over values, Python 3
for v in d.values():
...
# Iterate over values, Python 2
# In Python 2, dict.values() returns a copy
for v in d.itervalues():
...
# Iterate over keys and values, Python 3
for k, v in d.items():
...
# Iterate over values, Python 2
# In Python 2, dict.items() returns a copy
for k, v in d.iteritems():
...
反模式
for k, _ in d.items(): # instead: for k in d:
...
for _, v in d.items(): # instead: for v in d.values()
...
FIXME
- setdefault
- 通常最好使用 collections.defaultdict
dict.get 很實用,但使用 dict.get 然後檢查它是否為 None 來測試鍵是否在字典中是一種反模式,因為 None 是一個潛在的值,並且可以透過直接檢查來確定鍵是否在字典中。然而,如果 None 不是一個潛在的值,那麼使用 get 並與 None 進行比較是可以的。
簡單
if 'k' in d:
# ... d['k']
反模式(除非 None 不是一個潛在的值)
v = d.get('k')
if v is not None:
# ... v
- 來自鍵和值並行序列的字典
使用 zip,如下所示:dict(zip(keys, values))
如果找到則匹配,否則為 None
match = re.match(r, s)
return match and match.group(0)
... 如果沒有匹配,則返回 None,如果有匹配,則返回匹配內容。
- “Python 中的習語和反模式”, Moshe Zadka
- “PEP 20 -- Python 之禪”, Tim Peters