開發時加入註釋有助於描述思考過程,並幫助自己和其他人了解意圖,可以更輕鬆地發現錯誤、改進程式,以及在其他地方做更多應用。
單行註釋
加入註釋以 # 開頭,
# defining the start code
startCode = 50
也可加在程式碼後方,會被忽略,
startCode = 50 # defining the start code
注意不要加入無用的描述,
如同變數命名時不要取沒意義的名稱。
多行註釋
當要註釋的內容很多,或是撰寫文件、功能之類的,可以使用這種方式。
PEP8中建議單行不要超過79個字,一般情況則是會照公司或是團隊的開發習慣決定。
多行#開頭,
# PythonComments version 1.0.3
# -a (--all): show all features
# -h (--help): show the help
# .....
或是用""" 包住
"""
PythonComments version 1.0.3
-a (--all): show all features
-h (--help): show the help
.....
"""
三引號字串與 docstring
三引號在 Python 裡是字串的一種寫法,它會不會被當成文件,取決於它出現在哪個位置。PEP 257 對 docstring 的定義是:「A docstring is a string literal that occurs as the first statement in a module, function, class, or method definition. Such a docstring becomes the __doc__ special attribute of that object.」放在模組、函式、類別或方法定義的第一個陳述式,編譯器就會把它收進那個物件的 __doc__。
實際跑一次比較快。下面這個檔案,模組開頭一個三引號字串,函式 add_one 的第一行也是一個:
"""模組層的說明字串。"""
def add_one(x):
"""把 x 加一。"""
return x + 1
print(__doc__)
print(add_one.__doc__)
印出來是:
模組層的說明字串。
把 x 加一。
兩段文字都還在,程式執行到那兩行 print 的時候讀得到它們,這是 # 註解做不到的事。# 後面的內容不會變成任何物件的屬性,程式跑起來也沒有辦法回頭去看自己的註解寫了什麼。docstring 那一行在執行的時候被跳過,可是編譯的階段它被認出來、被搬進所在的類別、函式或模組的 __doc__,之後 help()、pydoc 或編輯器的提示視窗讀的都是這個欄位。
位置不對的三引號
下面這個 add_two,三引號沒有寫在第一行,而是接在 y = x + 2 後面:
def add_two(x):
y = x + 2
"""這段想拿來當註解"""
return y
print(add_two.__doc__)
印出來是 None。字串沒有變成 docstring,也沒有被存進任何地方,函式照樣算它的 y、照樣回傳。以語言規格來說,那一行是一個運算式陳述式,把後面那串運算式求值一次,求完就結束,沒有人接住結果。
把前面的 add_one 和這裡的 add_two 放在一起,各跑一次 help():
Help on function add_one in module __main__:
add_one(x)
把 x 加一。
Help on function add_two in module __main__:
add_two(x)
add_one 印得出那行說明,add_two 只剩下簽名。實際在寫程式的時候,這個差別會出現在編輯器上,游標停在函式名稱上跳出來的那個小視窗,內容就是從 __doc__ 來的,三引號放錯位置,那個視窗就是空的。
再往下一層看編譯完的結果。把 # 註解和三引號放進同一個函式,用 dis 印出 bytecode:
import dis
def add_two(x):
# 這是註解
y = x + 2
"""這段想拿來當註解"""
return y
dis.dis(add_two)
3 RESUME 0
5 LOAD_FAST_BORROW 0 (x)
LOAD_SMALL_INT 2
BINARY_OP 0 (+)
STORE_FAST 1 (y)
6 NOP
7 LOAD_FAST_BORROW 1 (y)
RETURN_VALUE
左邊那一欄是原始碼行號。y = x + 2 在第 5 行,展開成四個指令;return y 在第 7 行,兩個指令;第 6 行的三引號字串夾在中間,只剩下一個 NOP,那是行號留下來的位置,字串本身沒有在裡面。第 4 行的 # 這是註解 則是整行都沒有出現,# 在語法階段就被忽略掉,編譯器沒有看到它。(這裡跑的是 Python 3.14.3,bytecode 的細節每個版本會不太一樣,__doc__ 的部分則是規格寫死的。)
這兩行的處理在編譯之前就分開了。拿 ast.parse 把同一段程式碼讀進來,# 那行連節點都沒有,三引號那行則是一個 Expr 節點包著一個字串常數。
要寫多行的時候
那多行的內容要寫在哪呢?如果那段文字是給讀程式的人看的說明,多行 # 一直都可以用,位置隨便挑,編譯器一律忽略,也就是前面那種一行一行往下疊的寫法。如果寫的是這個模組、函式或類別本身在做什麼,放成 docstring 會多拿到一些東西,help() 讀得到,編輯器的提示讀得到,doctest、pydoc 這類工具也是從這個欄位拿資料。至於卡在函式中間的三引號字串,它不會讓程式壞掉,只是編譯器不會把它當文件,後面接手的人也未必看得出來那原本是想當註解用的。
型別提示與註解
型別提示普及之後,註解要寫的東西少了一塊。參數和回傳值是什麼型別,現在寫在簽名裡:
def add_two(x: int) -> int:
return x + 2
在型別提示之前,這一類資訊本來就是寫在註解裡的。PEP 484 也定義過這種形式,一個建議性、非強制的擴充,把函式的型別註記寫進 # type: 註解裡,理由是相容 Python 2.7 的程式碼。寫出來長這樣:
def add_two(x):
# type: (int) -> int
return x + 2
這兩種寫法工具都還讀得懂,差別在於簽名裡的型別和程式碼綁在一起,mypy、pyright 這類檢查器會逐行對,寫錯了會叫;註解裡的型別則是各走各的,改了一邊,另一邊不會跟著動。PEP 8 對註解的說法是:「Comments that contradict the code are worse than no comments.」
型別寫在簽名裡,做了什麼寫在名字和 docstring 裡,留給 # 的多半是程式碼本身講不出來的部分,為什麼要繞過某個 API、這個常數是誰定的、這段暫時的處理在等哪個 issue 修好。這些理由沒有語法可以檢查,型別檢查器不會看,linter 也不會看,過期了只能靠改程式的人自己記得回來改。


