程式碼除了用來執行外,更多的是用來讀。為了是程式碼的可讀性更強,很多程式語言都有自己的編碼規範。規範的制定是為了保持程式碼的一致性,以使程式碼更美觀和易讀。程式碼應該怎麼樣排版和編寫並不是絕對的,所以一些地方會有爭議。有時風格指南並不適用,最重要的知道何時不一致。當你無法判斷該怎麼做時,應該所參考下其他的例子。
本文僅是一個 Python 編碼風格的參考,並不是一個規定,規定必須要這麼去做。本文的目的應該是起一個指導作用,指導開發者去寫更易讀的程式碼。
一、程式碼編排
主要是縮排與空行的排版:
- 1、使用 4 個空格進行縮排(編輯器都可以完成此功能),不推薦使用製表符,更不能混合使用製表符和空格。
- 2、每行不超過 80 個字元,換行可以使用反斜槓,最好使用括號(Python 會將圓括號, 中括號和花括號中的行隱式的連線起來)。
- 3、類和頂層函式定義之間空兩行;類中的方法定義之間空一行;函式內邏輯無關段落之間空一行;其他地方儘量不要再空行。
二、文件編排
主要是整個原始碼檔案的佈局:
- 1、模組內容的順序:模組說明,模組文件字串,匯入語句,全域性變數或者常量,其他定義。
- 2、模組匯入部分順序:標準庫,第三方模組,自定義模組;各部分之間空一行。
- 3、不要在一個 import 語句中一次匯入多個模組,比如
import os, sys
不推薦。 - 4、匯入模組時應該使用合適的方式來避免命名衝突,例如在適當的時候才使用
from xx import xx
,儘量避免使用from xx imoprt *
。 - 5、在自已編寫的模組中,如果需要使用
from xx import *
時,應該在匯入語句後或者模組尾使用__all__
機制來限制匯入規則。
三、語句編排
- 1、通常每個語句應該獨佔一行。
- 2、不要在行尾加分號, 也不要用分號將多條語句放在同一行。
- 3、if/for/while 語句中,即使執行語句只有一句,也應儘量另起一行。
- 4、不要在返回語句(return)或條件語句(if/for/while)中使用括號,除非是用於實現行連線。
- 5、對於 if 語句, 在沒有 else 且語句比較短時,可以在一行完成(但不推薦),比如:
if foo: bar(foo)
. - 6、對於簡單的類定義,也可以在一行完成(但不推薦),比如定義一個異常:
class UnfoundError(Exception): pass
. - 7、函式和方法的括號中使用垂直隱式縮排或使用懸掛縮排。
# 一行寫不下時,有括號來連線多行,後續行應該使用懸掛縮排
if (this_is_one_thing
and that_is_another_thing):
do_something()
# 函式呼叫引數較多時,對準左括號
f = foo(a, b,
c, d)
# 不對準左括號,但加多一層縮排,以和後面內容區別
def long_function_name(
a, b, c,
d, e):
print(a, b, c, d, e)
# 列表、元組、字典以及函式呼叫時可以讓右括號回退,這樣更加美觀
l = [
1, 2, 3,
4, 5, 6,
]
result = some_function(
`a`, `b`, `c`,
`d`, `e`, `f`,
)
四、空格使用
總體原則,避免不必要的空格。
- 1、各種右括號前不要加空格。
- 2、逗號、冒號、分號前不要加空格,但應該在它們後面加(除了在行尾)。
- 3、函式的左括號前不要加空格。如 Func(1)。
- 4、序列的左括號前不要加空格。如 list[2]。
- 5、操作符左右各加一個空格,不要為了對齊增加空格。
- 6、函式預設引數使用的賦值符左右省略空格。
良好的風格:
spam(ham[1], {eggs: 2})
if x == 4:
print x, y; x, y = y, x
f = foo(1, 2, 3)
ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
ham[lower:upper], ham[lower:upper:], ham[lower::step]
ham[lower+offset : upper+offset]
ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)]
ham[lower + offset : upper + offset]
x = 1
y = 2
long_variable = 3
def foo(a, b, c=0):
return moo(m=a, n=b, o=c)
不好的風格:
spam( ham[ 1 ], { eggs: 2 } )
if x == 4 :
print x , y ; x , y = y , x
f = foo (1, 2, 3)
ham[lower + offset:upper + offset]
ham[1: 9], ham[1 :9], ham[1:9 :3]
ham[lower : : upper]
ham[ : upper]
x = 1
y = 2
long_variable = 3
def foo(a, b, c = 0):
return moo(m = a, n = b, o = c)
五、註釋
總體原則,錯誤的註釋不如沒有註釋。所以當一段程式碼發生變化時,第一件事就是要修改註釋。註釋儘量使用英文,最好是完整的句子,首字母大寫,句後要有結束符,結束符後跟兩個空格,開始下一句。如果是短語,可以省略結束符。註釋應該在 #
後加一個空格才開始寫註釋內容。
- 1、塊註釋,在一段程式碼前增加的註釋。段落之間用只有 ‘#’ 的行間隔。比如:
# Description : Module config.
#
# Input : None
#
# Output : None
- 2、行註釋,在一句程式碼後加註釋。應該儘量在語句後空兩格後再開始註釋。當有連續的行註釋時,為了美觀可以讓 ‘#’ 對齊。 在語句比較長時,應該儘量少使用行註釋。比如:
person = {
"name": "huoty", # 姓名
"age": 26, # 年齡
"stature": 169, # 身高
"weight": 60, # 體重
}
print person # 輸出資訊
- 3、對類或者函式的說明,儘量不要在其定義的前一行或者後一行用塊註釋的形式來說明,而應該使用文件字串(docstring)
- 4、使用
TODO
註釋來標記待完成的工作,團隊協作中,必要的時候應該寫上你的名字或者聯絡方式,比如:
# TODO(sudohuoty@gmail.com): Use a "*" here for string repetition.
# TODO(Huoty) Change this to use relations.
- 5、避免無謂的註釋。你見過哪些奇趣的程式碼註釋?
# 你可能會認為你讀得懂以下的程式碼。但是你不會懂的,相信我吧。
# 要是你嘗試玩弄這段程式碼的話,你將會在無盡的通宵中不斷地咒罵自己為什麼會認為自己聰明到可以優化這段程式碼。
# so,現在請關閉這個檔案去玩點別的吧。
# 程式設計師1(於2010年6月7日):在這個坑臨時加入一些調料
# 程式設計師2(於2011年5月22日):臨你個屁啊
# 程式設計師3(於2012年7月23日):樓上都是狗屎,鑑定完畢
# 程式設計師4(於2013年8月2日):fuck 樓上,三年了,這坑還在!!!
# 程式設計師5(於2014年8月21日):哈哈哈,這坑居然坑了這麼多人,幸好我也不用填了,系統終止執行了,you`re died
六、文件描述
- 1、儘量為所有的共有模組、函式、類、方法寫
docstring
。 - 2、前三引號後不應該換行,應該緊接著在後面概括性的說明模組、函式、類、方法的作用,然後再空一行進行詳細的說明。後三引號應該單獨佔一行。比如:
"""Convert an API path to a filesystem path
If given, root will be prepended to the path.
root must be a filesystem path already.
"""
- 2、函式和方法的 docstring 層次順序大致為概述、詳細描述、引數、返回值、異常,一般不要求描述實現細節,除非其中涉及非常複雜的演算法。大致的層次結構如下所示:
"""函式或方法的概述
詳細的描述資訊……
詳細的描述資訊……
引數說明
--------
引數1:...
引數2:...
返回值:
...
異常:
異常1:...
異常2:...
"""
一個參考示例:
"""Start a kernel for a session and return its kernel_id.
Parameters
----------
kernel_id : uuid
The uuid to associate the new kernel with. If this
is not None, this kernel will be persistent whenever it is
requested.
path : API path
The API path (unicode, `/` delimited) for the cwd.
Will be transformed to an OS path relative to root_dir.
kernel_name : str
The name identifying which kernel spec to launch. This is ignored if
an existing kernel is returned, but it may be checked in the future.
Return a kernel id
"""
- 3、類的 docstring 的層次順序大致為概述、詳細描述、屬性說明。如果類有公開屬性值時,應該儘量在 docstring 中進行說明。如下所示:
"""這裡是類的概述。
詳細的描述資訊……
詳細的描述資訊……
屬性(Attributes):
-----------------
屬性1: ...
屬性2: ...
"""
七、命名規範
- 1、模組命名儘量短小,使用全部小寫的方式,可以使用下劃線。
- 2、包命名儘量短小,使用全部小寫的方式,不可以使用下劃線。
- 3、類的命名使用駝峰命令的方式,即單詞首字元大寫,類名應該全部使用名詞。
- 4、異常命令應該使用加
Error
字尾的方式,比如:HTTPError。 - 5、全域性變數儘量只在模組內有效,並且應該儘量避免使用全域性變數。
- 6、函式命名使用全部小寫的方式,使用下劃線分割單詞,並採用動賓結構。
- 7、常量命名使用全部大寫的方式,使用下劃線分割單詞。
- 8、類的屬性(方法和變數)命名使用全部小寫的方式,使用下劃線分割單詞。
- 9、變數、類屬性等命令儘量不要使用縮寫形式,除了計數器和迭代器,儘量不要使用單字元名稱。
- 10、類的方法第一個引數必須是 self,而靜態方法第一個引數必須是 cls。
- 11、在模組中要表示私有變數或者函式時,可以在變數或者函式前加一個下劃線
_foo
,_show_msg
來進行訪問控制。 - 12、在 Python 中沒有諸如 public、private、protected 等修飾符,而在類的定義中往往會有類似這樣的需求,那麼可以在屬性或者方法前加一個下劃線表示 protected,加兩個下劃線來表示 private。加兩個下劃線的變數或者方法沒法直接訪問。比如:類 Foo 中宣告
__a
, 則不能用Foo.__a
的方式訪問,但可以用Foo._Foo__a
的方式訪問。`
八、程式入口
Python 屬於指令碼語言,程式碼的執行是通過直譯器對程式碼檔案進行逐行解釋執行來完成的。它不像其他程式語言那樣有統一的入口程式,比如 Java 有 Main 方法,C/C++ 有 main 方法。Python 的程式碼檔案除了可以被直接執行外,還可以作為模組被其他檔案匯入。所有的頂級程式碼在模組匯入時都會被執行,當希望模組被匯入時,應該避免主程式被執行。這樣就需要把主程式放到 if __name__ == `__main__`
程式碼塊中,比如:
def main():
...
if __name__ == `__main__`:
main()
一個包除了能夠被匯入外,也可以通過 python -m package
的方式被直接執行,前提是包中需要有 __main__.py
,這個檔案可以說是包的程式入口,包中有了這個檔案就可以用 Python 的 -m
引數來直接執行。
九、編碼建議
- 1、儘可能使用 `is` 和 `is not` 取代 `==`,比如 if x is not None 要優於 if x != None,另外用 if x 效率更高。
Note: 等於比較運算子(==) 會呼叫左運算元的 __eq__
函式,這個函式可以被其任意定義,而 is 操作只是做 id 比較,並不會被自定義。同時也可以發現 is 函式是要快於等於運算子的,因為不用查詢和執行函式。
- 2、用 “is not” 代替 “not … is”,前者的可讀性更好。
- 3、使用基於類的異常,每個模組或包都有自己的異常類,此異常類繼承自 Exception。
- 4、異常中儘量不要使用裸露的 except,except 後應該跟具體的 exceptions。
- 5、使用 startswith() 和 endswith() 代替切片進行序列字首或字尾的檢查。
- 6、使用 isinstance() 比較物件的型別,而不是 type(),比如:
# Yes:
if isinstance(obj, int)
# No:
if type(obj) is type(1)
- 7、判斷序列是否為空時,不用使用 len() 函式,序列為空時其 bool 值為 False,比如:
# Yes:
if not seq
if seq
# No:
if len(seq)
if not len(seq)
- 8、字串後面不要有大量拖尾空格。
- 9、使用 join 合併的字串,字串方法 join 可以合併 list、tuple、iterator 中的元素,效率比連線符 + 高。
- 10、使用
while 1
比while True
更快。 - 11、使用
**
比pow
快 10 倍以上。 - 12、使用迭代器和生成器代替列表等資料結構效率更高,使用列表(字典)解析式和生成器表示式比用迴圈效率更高。
- 13、避免在迴圈中用 + 或 += 來連續拼接字串。因為字串是不變型,這會毫無必要地建立很多臨時物件,從而成為二次方級別的運算量而不是線性運算時間。
- 14、多去了解標準庫,標準庫中用很多好用的功能,能夠更優雅的解決問題,如 pkgutil.get_data()、operator.methodcaller() 等等。