一篇 針對Python開發的“最佳實踐精選”指南。
總體原則
價值標準
- “為別人開發你也想要使用的工具。” ——Kenneth Reitz
- “簡潔性總是勝過可用性。” ——Pieter Hintjens
- “滿足90%的使用場景。忽略那些說不的人。” ——Kenneth Reitz
- “優美勝過醜陋。” ——PEP 20
- 為開源(甚至是閉源專案)而開發。
一般開發準則
- “明確勝過含隱含。” —— PEP 20
- “易讀亦有價。” —— PEP 20
- “人人都能打補丁。” —— 可汗學院開發文件
- 一旦發現破窗(設計錯誤,決策失誤或編碼質量低),馬上修補。
- “現在做也要勝過不去做。” —— PEP 20
- “測試要徹底。撰寫新功能文件。”
- 人力驅動型開發,比測試驅動型開發更重要。(譯者:原文為Even more important that Test-Driven Development–Human-Driven Development,譯者認為more important that應該是more important than,應該是作者筆誤,否則意思不通,)
- 這些準則可能——應該是很可能——會改變。
特殊準則
風格
感覺合理的話,就遵循PEP 8。
命名
- 變數、函式、方法、包、模組
小寫,並使用下劃線分隔單詞(lower_case_with_underscores)
- 類、異常
首字母大寫(CapWords)
- 受保護的方法和內部函式
單下劃線開頭(_single_leading_underscore(self, …))
- 私有的方法
雙下劃線開頭(__double_leading_underscore(self, …))
- 常量
字母全部大寫,單詞間用下劃線分隔(ALL_CAPS_WITH_UNDERSCORES)
一般命名準則
儘量不要使用只有一個字母的變數名(例如,l,I,O等)。
例外:在很簡短的程式碼塊中,如果變數名的意思可以從上下文明顯地看出來。
沒問題
1 2 |
for e in elements: e.mutate() |
避免冗餘描述。
正確的做法
1 2 3 4 |
import audio core = audio.Core() controller = audio.Controller() |
錯誤的做法
1 2 3 4 |
import audio core = audio.AudioCore() controller = audio.AudioController() |
“反向記法”更好。
正確的做法
1 2 3 |
elements = ... elements_active = ... elements_defunct = ... |
錯誤的做法
1 2 3 |
elements = ... active_elements = ... defunct_elements ... |
避免使用getter和setter方法。
正確的做法
1 |
person.age = 42 |
錯誤的做法
1 |
person.set_age(42) |
縮排
用4個空格符——永遠別用Tab製表符。就說這麼多。
模組引用
引用整個模組,而不是模組中的單個識別符號。舉個例子,假設一個cantee模組下面,有一個sessions.py檔案,
正確的做法
1 2 3 |
import canteen import canteen.sessions from canteen import sessions |
錯誤的做法
1 2 |
from canteen import get_user # Symbol from canteen/__init__.py from canteen.sessions import get_session # Symbol from canteen/sessions.py |
例外:如果第三方程式碼的文件中明確說明要單個引用。
理由:避免迴圈引用。看這裡。
把程式碼引用部分放在檔案的頂部,按下面的順序分成三個部分,每個部分之間空一行。
- 系統引用
- 第三方引用
- 本地引用
理由:明確顯示每個模組的引用來源。
文件
遵循PEP 257提出的文件字串準則。reStructuredText (reST) 和Sphinx有助於確保文件符合標準。
對於功能明顯的函式,撰寫一行文件字串。
1 |
"""返回``foo``的路徑名.""" |
多行文件字串應包括:
- 一行摘要
- 合適的話,請描述使用場景
- 引數
- 返回資料型別和語義資訊,除非返回
None
“””訓練模型,用來對Foo和Bar分類。
用法::
1 2 3 4 5 6 7 8 9 10 11 |
"""Train a model to classify Foos and Bars. Usage:: >>> import klassify >>> data = [("green", "foo"), ("orange", "bar")] >>> classifier = klassify.train(data) :param train_data: `(color, label)`形式的一個元組列表。 :rtype: A :class:`Classifier <Classifier>` """ |
注意
使用主動詞(“返回”),而不是描述性的單詞(“返回值”)。 在類的文件字串中為__init__
方法撰寫文件。
1 2 3 4 5 6 7 8 9 |
class Person(object): """A simple representation of a human being. :param name: A string, the person's name. :param age: An int, the person's age. """ def __init__(self, name, age): self.name = name self.age = age |
關於註釋
儘量少用。與其寫很多註釋,不如提高程式碼可讀性。通常情況下,短小的方法比註釋更有效。
錯誤的做法
1 2 3 |
# If the sign is a stop sign if sign.color == 'red' and sign.sides == 8: stop() |
正確的做法
1 2 3 4 5 |
def is_stop_sign(sign): return sign.color == 'red' and sign.sides == 8 if is_stop_sign(sign): stop() |
但是的確要寫註釋時,請牢記:“遵循斯托克與懷特所寫的《英文寫作指南》。” —— PEP 8
每行的長度
不要過分在意。80到100個字元都是沒問題的。
使用括號延續當前行。
1 2 3 4 5 6 |
wiki = ( "The Colt Python is a .357 Magnum caliber revolver formerly manufactured " "by Colt's Manufacturing Company of Hartford, Connecticut. It is sometimes " 'referred to as a "Combat Magnum". It was first introduced in 1955, the ' "same year as Smith &amp; Wesson's M29 .44 Magnum." ) |
測試
儘量爭取測試全部程式碼,但也不必執著於覆蓋率。
一般測試準則
- 使用較長的、描述性的名稱。通常情況下,這能避免在測試方法中再寫文件。
- 測試之間應該是孤立的。不要與真實地資料庫或網路進行互動。使用單獨的測試資料庫,測試完即可銷燬,或者是使用模擬物件。
- 使用工廠模式,而不是fixture。
- 別讓不完整的測試通過,否則你就有可能忘記。你應該加上一些佔位語句,比如
assert False, "TODO: finish me"
。
單元測試
- 每次聚焦一個很小的功能點。
- 測試速度要快,但是速度慢總比不測試好。
- 通常,每一個類或模型都應該有一個測試用例類。
1 2 3 4 5 6 7 8 9 |
import unittest import factories class PersonTest(unittest.TestCase): def setUp(self): self.person = factories.PersonFactory() def test_has_age_in_dog_years(self): self.assertEqual(self.person.dog_years, self.person.age / 7) |
功能測試
功能測試是更高層次的測試,更接近終端使用者如何與應用互動這一層面。通常用在網路應用與圖形應用測試。
- 按照場景撰寫測試。測試用例的測試方法命名應該看上去像場景描述。
- 在編寫程式碼之前,通過註釋說明具體場景資訊。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import unittest class TestAUser(unittest.TestCase): def test_can_write_a_blog_post(self): # Goes to the her dashboard ... # Clicks "New Post" ... # Fills out the post form ... # Clicks "Submit" ... # Can see the new post ... |
請注意,測試用例的類名稱和測試方法的名稱放在一起,就像是“測試一名使用者能否釋出博文”。