Last Updated on 2021-07-25 by Clay
最近有個比較大一點的個人專案準備收尾,並準備交接給朋友接手,看他後續想要新增什麼樣的功能。有鑑於此,我開始考慮是否將程式碼做個徹底的整理。
其實我覺得重要的是 Coding Style 要從頭到尾保持一致, …… 有時候前後寫法不同會讓人看得很痛苦,真的。另外就是需要跟團隊(我的團隊只有兩人啊哈哈)溝通好彼此撰寫的程式碼風格。
不過,既然決定了要徹底整理自己的程式碼給別人接手,那麼我想參考高手、大神的建議的風格規範總是好的。本篇文章主要就是紀錄一份名為『Google 開源專案風格指南』的筆記,讓我能按照自己整理的版面,去參照、進而修改自己的專案,使其符合這開源專案風格指南所建議的風格。
在此,非常感謝原作者:
– Amit Patel
– Antoine Picard
– Eugene Jhong
– Jeremy Hylton
– Matt Smart
– Mike Shields
以及中文版風格指南的翻譯:
– guoqiao
– xuxinkun
那麼,以下便是 Google 開源專案風格指南中所定義的 Python 風格規範:
規範 | 解釋 |
---|---|
分號 | 不要在行尾加分號,也不要用分號將兩條命令放在同一行 |
行長度 | 每行不超過 80 個字符 |
括號 | 寧缺勿濫地使用括號 |
縮排 | 使用 4 個空格來縮進代碼 |
空行 | 頂級定義之間空兩行,方法定義之間空一行 |
空格 | 按照標準的排版規範來使用標點兩邊的空格 |
Shebang | 大部分的 .py 文件不必以 #! 作為文件開頭 |
註釋 | 確保對模塊、函數、方法和行內註釋使用正確的風格 |
類 | 如果一個類不繼承自其它類,就顯式地從 Object 繼承 |
字串 | 盡量使用 % 或格式化方法格式化字串 |
文件和 Sockets | 在文件和 Sockets 結束時,顯式地關閉 |
TODO 註釋 | 為臨時程式碼使用 TODO 註釋 |
導入格式 | 每個導入應該獨佔一行 |
語句 | 每個語句應該獨佔一行 |
訪問控制 | 對於瑣碎不重要的訪問函數,使用全域變數來取代 |
命名 | 應避免單字符、Dash、雙下底線等命名方式 |
Main | 即使是腳本文件,也應是可導入的 |
以下的介紹會有條列式的敘述、以及範例程式。唯有粗體字為我個人見解,可能有誤,酌情參考。
分號
不要在行尾加分號,也不要用分號將兩條命令放在同一行
# No a = 1; b = 1; c = a + b # Yes a = 1 b = 1 c = a + b
行長度
每行不超過 80 個字符
這很好理解,過長的程式碼會造成閱讀理解上的障礙。
括號
寧缺勿濫地使用括號
- 不要在函式返回時使用
- 不要在條件式中使用
- 元組 (Tuple) 可以使用
# No return (x, y) if (x and y): ... # Yes return x, y if x and y
我個人傾向於這是習慣問題 …… 不過我同意不要亂加括號程式碼看起來比較清楚。
縮排
使用 4 格空白來進行縮排
- 不要使用 TAB
- 換行時元素對齊
# No id2value = { 1: 10, 2: 20, 3: 30, } # Yes id2value = { 1: 10, 2: 20, 3: 30, }
由於 Python 嚴格地需要使用『縮排』來區分程式碼區塊,比方說 For 迴圈或是函式,故夾雜使用空白或 TAB 會導致 Python 直譯器發生『縮排錯誤』的報錯。
另外元素對齊,是為了美觀、也是為了減輕閱讀者的負擔。
空行
頂級定義之間空兩行,方法定義之間空一行
- 函式 (Function) 之間空兩行
- 類 (Class) 之間空兩行
- 方法 (Method) 之間空一行
- 某些地方覺得適合,就空一行 (?)
# No def x(): ... def y(): ... # Yes def x(): ... def y(): ...
# No class x: def __init__(self): ... def process(self): ... # Yes class x: def __init__(self): ... def process(self): ...
這部份就是統一格式罷了,最大的問題是『覺得適合就空一行』這個敘述非常含糊 —— 我自己是在遇到迴圈或條件判斷時會在上方空一行。
空格
按照標準的排版規範來使用標點兩邊的空格
- 不要在逗號、分號、冒號前方加空格,但後方則需要
- 操作符左右都應加上空格
# No a,b,c=1,2,3 # Yes a, b, c = 1, 2, 3
Shebang
大部分的 .py 文件不必以 #! 作為文件開頭
註釋
確保對模塊、函數、方法和行內註釋使用正確的風格
- Python 慣例使用三重雙引號 “”” 來當作文檔字串符
- 註釋可描述每個參數名稱與功用 (Args)
- 註釋可描述返回值的類型與語義 (Returns)
- 註釋可描述函式接口的所有異常 (Raises)
def fetch_bigtable_rows(big_table, keys, other_silly_variable=None): """Fetches rows from a Bigtable. Retrieves rows pertaining to the given keys from the Table instance represented by big_table. Silly things may happen if other_silly_variable is not None. Args: big_table: An open Bigtable Table instance. keys: A sequence of strings representing the key of each table row to fetch. other_silly_variable: Another optional variable, that has a much longer name than the other args, and which does nothing. Returns: A dict mapping keys to the corresponding table row data fetched. Each row is represented as a tuple of strings. For example: {'Serak': ('Rigel VII', 'Preparer'), 'Zim': ('Irk', 'Invader'), 'Lrrr': ('Omicron Persei 8', 'Emperor')} If a key from the keys argument is missing from the dictionary, then that row was not found in the table. Raises: IOError: An error occurred accessing the bigtable.Table object. """ pass
- 要解釋程式碼中帶有技巧性的部份,使用 # 註釋於行尾
- 註釋應距離程式碼至少 2 個空格
# No if i & (i-1) == 0:# true iff i is a power of 2 # Yes if i & (i-1) == 0: # true iff i is a power of 2
類
如果一個類不繼承自其它類,就顯式地從 Object 繼承
- 繼承 Object 是為了使屬性 (Properties) 正常運作
# No class SampleClass: pass # Yes class SampleClass(object): pass
字串
盡量使用 % 或格式化方法格式化字串
- 避免使用 + 或是 += 來增加字符串,會創建不必要的臨時對象
- 改將字串加入列表,使用 join() 連接字串
# No string = '' for item in items: string += item # Yes string = [] for item in items: string.append(item) string = ''.join(item)
文件和 Sockets
在文件和 Sockets 結束時,顯式地關閉
- 不關閉會消耗系統資源
- 不同 Python 記憶體管理技術不保證是同樣實現
- 推薦使用 with 管理文件
這是我經常會犯的錯誤 …… 在仔細查看這篇文檔之前沒想過這麼危險。
TODO 註釋
為臨時程式碼使用 TODO 註釋
- TODO 註釋應於開頭包含 “TODO” 字串
- 應該留有開發者名字、Email
- 解釋要做什麼
# TODO([email protected]): Use a "*" here for string repetition. # TODO(Zeke) Change this to use relations.
導入格式
每個導入應該獨佔一行
# No import os, sys # Yes import os import sys
- 導入應於文件開頭
- 導入應於模塊註釋和文檔字串後
- 導入應於程式碼前
導入應有固定順序:
- 標準模組導入
- 第三方模組導入
- 其他
導入順序按字母排序:
import foo from foo import bar from foo.bar import baz from foo.bar import Quux from Foob import ar
語句
每個語句應該獨佔一行
# No if foo: bar(foo) else: baz(foo) try: bar(foo) except ValueError: baz(foo) # Yes if foo: bar(foo) else: baz(foo) try: bar(foo) except ValueError: baz(foo)
訪問控制
對於瑣碎不重要的訪問函數,使用全域變數來取代
這邊文檔中只寫著要嘗試接受 Pythonic 哲學 XDDD
我想,除非真的太過消耗效能,否則直接使用全域變數其實是很方便的。
命名
應避免單字符、Dash、雙下底線等命名方式
- 除了計數與迭代,否則不應使用單字符
- 不應使用 dash (-) 符號,該使用於套件或模組名稱
- __xx__ 為 Python 保留名稱
推薦規範:
Type | Public | Internal |
---|---|---|
Modules (模組) | lower_with_under | lower_with_under |
Packages (套件) | lower_with_under | |
Classes (類) | CapWords | _CapWords |
Exceptions (例外) | CapWords | |
Functions (函式) | lower_with_under() | _lower_with_under() |
Global/Class Constant (常數) | CAPS_WITH_UNDER | _CAPS_WITH_UNDER |
Global/Class Variables (變數) | lower_with_under | _lower_with_under |
Instance Variables | lower_with_under | _lower_with_under (protect) __lower_with_under(private) |
Method Names | lower_with_under() | _lower_with_under() (protect) __lower_with_under() (private) |
Function/Method Parameters (參數) | lower_with_under | |
Local Variables | lower_with_under |
Main
即使是腳本文件,也應是可導入的
# Yes def main(): ... if __name__ == '__main__': main()
慚愧 ….. 我有很多只是單純執行、測試某些元件的程式,都像是腳本一樣由上到下寫。
不過真的開發要使用的功能,倒是會好好遵守著這樣的格式規範。
Pingback: 【程式優化】Google Coding Style - 01 | variable/ function python 等命名原則 - 嗡嗡的隨手筆記