Python最最佳指南

Python 的「最最佳作法」(the Best of Best Practices)指南。(原文 by @Sloria)

概論

價值觀

  • 「不要做己所不欲的工具給人用」 - Kenneth Reitz
  • 「簡單永遠勝過多功能」 - Pieter Hintjens
  • 「滿足 90% 的使用情境,不要管奧客」 - Kenneth Reitz
  • 「優美勝過醜陋」 - PEP 20
  • 「為開源貢獻」(即便最終是為了閉源項目)

一般開發準則

  • 「顯明勝過隱含」 - PEP 20
  • 「可讀性很重要」 - PEP 20
  • 「所有人都可以解決所有問題」 - Khan Academy Development Docs
  • 「一旦發現破窗(糟糕的設計、錯誤的決定、劣質代碼)立刻修復」
  • 「現在就做好過一直不做」 - PEP 20
  • 無情地測試,為新功能寫文檔
  • 比測試驅動開發更重要的是人類驅動開發
  • 這些準則也許,很可能,會改變

特別

風格

除非有適當理由,遵循 PEP 8

命名

  • 變量,函數,方法,包,模塊
    • lower_case_with_underscores
  • 類與例外
    • CapWords
  • 保護或內部方法
    • _single_leading_underscore(self, ...)
  • 私有方法
    • __double_leading_underscore(self, ...)
  • 常數
    • ALL_CAPS_WITH_UNDERSCORES
一般命名準則

不要用單字母變量(特別是 l, O, I)(譯註:容易與數字搞混)

例外: 在非常短的塊裡,其含意從附近上下文很明顯時可以接受。

這樣可以

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。以上。

import

import 整個模塊而不是單獨的符號。例如如果頂層模組 canteen 下有 canteen/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

例外: 除非某些第三方代碼的文件中明確指示 import 單個符號。

理由: 這可以避免循環 import,見這個例子

把所有 import 放在文件頂部,分三小節,每小節用一個空行隔開,三個小節分別是

  1. 系統 import
  2. 第三方 import
  3. 本地源 import

理由: 容易看出模塊出自哪裡。

文檔

遵循 PEP 257 的文檔字串規範。 reStructured TextSphinx 可以幫你強制這些規範。

功能明顯的函數只需要一行文檔字串。

1
"""Return the pathname of ``foo``."""

多行文檔字串應包括

  • 一行總結
  • 使用情境,若適合
  • 參數
  • 回傳值的類型與意思,除非回傳值是 None
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: A list of tuples of the form ``(color, label)``.
:rtype: A :class:`Classifier <Classifier>`
"""

備註

  • 使用命令體(”Return”)不要記敘體(”Returns”)
  • __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 & Wesson's M29 .44 Magnum."
)

測試

追求 100% 覆蓋,但不要太執著。

一般測試準則

  • 用長而描述性的名字命名用例。這樣通常就不需要在測試裡寫文檔字串。
  • 用例應該彼此分離,不要在用例間共享一個資料庫或網路連接,為每個用例使用一個單獨的測試用資料庫並在用完後拆除,或是使用模擬。
  • 偏好用工廠來控制變量。
  • 絕對不要讓沒寫完的用例通過,否則你可能會忘記他們。相反,預留一個 assert False, "TODO: 完成我"

單元測試

  • 每次關注一個小功能
  • 最好很快完成,但一個跑得很慢的測試勝過沒測試
  • 一個用例的類對應一個類或模型通常是合理的。
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
...

注意測試用例跟測試方法合起來是一句話「Test A User can write a blog post」。

靈感來自…