寫好 Python Docstring 的 4 個層次——從簡單到詳細
文章目錄
在〈Docstring 的重要性——《Python 功力提升的樂趣》〉一文中,我提到了:
Docstring 也不是有寫就行,還需要從「讀者(也就是你的同事)」的角度去思考與表達。不然看起來會很像開發者的自言自語——沒人看得懂。
在工作上使用 Python 多年,即便是經驗豐富的 Python 工程師,很多人也未必養成了撰寫 Docstring 的習慣。
畢竟在日常趕專案的場景下,一段程式碼能否 work,往往比「好讀、好理解」更重要——至少更急迫。
然而,當專案逐漸擴大、團隊成員增加時,沒有良好的 Docstring,協作上的痛苦就會慢慢浮現。
Docstring 不是單純寫給自己的備忘,而是寫給「下一個會看這份程式的人」。
當你開始用這個角度思考,你會發現,寫好 Docstring 並不是加分項,而是基本功。
那要怎樣才能寫好 Python Docstring 呢?
作為 Docstring 信奉者,本文將分享我在過去工作經驗中所累積的 Python Docstring 寫作心得,並整理成教學指南。
什麼是 Docstring?
Docstring(Documentation String)是 Python 中用來描述模組、類別、函式(包括類別中的方法)用途的字串。
它通常放在定義語法(比如def xxx
)之後,由三個雙引號("""
)包覆,方便工具或 IDE 自動擷取,生成文件或顯示說明。
良好的 Docstring 不僅可以幫助他人快速理解程式碼,也能在開發、維護、協作中扮演關鍵角色。
它是「程式碼即文件」理念的具體實踐方式之一。
Docstring 這種設計在許多語言中都有,例如 Java 的 JavaDoc、C# 的 XML Comments 等,Python 版本因為簡單好用,更成為日常開發不可或缺的一環。
本文主旨與目標讀者
我將透過具體的案例來闡述如何寫好 Docstring。
本文主要的目標讀者,是那些已經在用 Python 開發專案、參與團隊協作的工程師們——在團隊協作的場景下,Docstring 的重要性會遠超過個人開發。
換言之,Docstring 象徵的是「溝通的藝術」😎
當專案愈來愈複雜,或有新成員加入時,Docstring 會展現它的價值:
- 協助他人快速理解你的函式用途與用法
- 減少口頭或書面文件的補充說明成本
- 提升程式碼自我解釋(self-explanatory)的能力
- 幫助自己未來回頭維護時快速理解程式
本文將從「實用角度」出發,分享如何根據不同情境,靈活且有效地撰寫(或選擇不寫)Python Docstring。
Docstring 的基本格式與風格
Python Docstring 的格式與風格並沒有固定的規範,但有一些通用的慣例與建議。
按照〈PEP 257 – Docstring Conventions〉的建議,Docstring 必須用三個雙引號("""
)包起來。
以下是 Python 社群中常見的 Docstring 風格:
- Google Style
- Numpy Style
- reStructuredText (Sphinx)
本文範例皆採用 Google Style,這是我過去工作中使用的風格,它簡潔且容易閱讀。
不過,我覺得風格不是重點,清楚與一致才是關鍵。
下一篇文章會介紹如何用 VS Code 的套件快速生成各種風格的 Docstring,這裡就先不深入。
不是每個函式都需要 Docstring
Docstring 可以寫在模組、類別與函式的開頭,我們在這裡主要討論函式的 Docstring 撰寫,因為它是最常用,同時也是最需要說明的程式碼元件。
在開始介紹四個層次之前,有一個觀念必須先釐清:不是每個函式都需要 Docstring。
有些函式,一看就知道用途,例如簡單的資料轉換、資料初始化,或是完全沒有參數。
這些函式若執意要寫 Docstring,反而可能造成雜訊。
簡單來說:
如果一個函式「一望即知」,那就不需要為寫而寫。
Docstring 的重點在於補充「別人不容易一眼看懂的東西」,它是一種基於「同理心」的實踐,而非教條。
Docstring 的四個層次
根據不同的情境與複雜度,決定 Docstring 撰寫的詳細程度很重要。
不過,必須承認,在多數情況下,我們都是透過工具輔助——而工具產生的通常是下述第三層次(對輸入輸出有完整說明)的 Docstring 框架。
因此,如果把每一個 Docstring 都寫到第三層次,我也不反對。畢竟它最常見。
我自己則會依照程式碼的複雜度,進行一定程度的調整。
無論如何,在實務情境中,有寫通常比沒寫好☺️
一、只有主旨的 Docstring
有些簡單但需要了解「大概意圖」的函式,只需要一句話就足夠:
1 | def is_internal_user(email): |
這樣的 Docstring 雖然簡單,卻仍比沒有好。
這段主旨的文眼在於「判斷」二字,它直接說明了程式的行為與意圖,讓別人一眼就能知曉這個函式打算做什麼——判斷並回傳布林值。
錯誤示範
要寫好一行式的 Docstring 有時並不容易,我們偶爾會陷入過於細節的描述,以下是個不太好的示範:
1 | """透過 SQL 查詢比對用戶的電子郵件是否為內部使用者""" |
這種寫法就太底層了,提到了那些不必要的細節(SQL 查詢),容易讓人分心。
Docstring 應描述高階意圖,而非內部細節。
二、主旨 + 說明(加上 context,讓人看得懂用在什麼情境)
當函式稍微複雜,或有特定使用情境時,可以有更多的說明。
1 | def clear_user_session(user_id): |
主旨和說明中間推薦有一行空白。
這樣不僅格式美觀,也是許多 IDE 或文件生成工具提取摘要的重要依據。
直接在 Docstring 說明,能避免不必要的推測,這樣的補充既實用又貼心。
說明的變化與彈性
說明沒有一定規則,重點在於補充那些「使用者不容易自行推測」的資訊。
你覺得最需要提醒使用者(開發者)什麼,就寫下這些內容。
只要能幫助他人更好理解,怎麼寫都可以。
對於複雜的情境,說明可能會很長(常見於開源專案的 Docstring),這是正常的!
3. 主旨 + 參數與回傳
若函式有多個參數,清楚列出參數與回傳型態與說明,能大幅降低理解門檻。
1 | def calculate_discounted_price(original_price, discount_rate): |
這個第三層次,大概是我們寫 Docstring 時最常使用的格式!
函式的參數與回傳,是最容易讓人感到困惑的地方。
我們可以想像:同事寫了一個共用函式,現在你要去調用它,你肯定需要知道,它應該有什麼樣的 input,以及調用之後會得到什麼樣的結果。
如果不清楚這些,根本無法正常使用這個函式。
因此,在有參數的函式裡,每個參數代表什麼,包括參數型別,都很重要。
Docstring 的價值在這裡得到了充分發揮。
說明的轉換
細心的你可能發現了,這個例子沒有一段獨立的說明。
原因很簡單,在這個地方的說明,已經化為了對 input 跟 output 的闡釋。
額外再寫一段說明,可能會有些冗餘。(但也不盡然)
這裡只是要提醒,在這種有參數的情境下,說明落段往往會被省略。
四、主旨 + 說明 + 參數與回傳 + 範例
第四層次的重點是舉例。
有了舉例,就算不清楚函式細節,也能迅速理解它的用途與結果。
這個場景中,資料庫裡面有一個欄位已經儲存了格式化過的 Mac 地址,長這樣:1234567890AB
。
我們要從 API 回傳給前端的時候,需要做一些處理,它讓變成這樣:12 34 56 78 90 AB
。
它真正的邏輯只有這一行:
1 | " ".join(mac_address[i : i + 2] for i in range(0, 12, 2)) |
但如果你看程式碼,應該不太可能一眼就看出它處理的結果啦!——除非你是 AI 🤖
但加上舉例之後就一目了然了。
1 | def format_output_mac_address(mac_address: str) -> str: |
使用範例能讓讀者一秒理解「怎麼用」與「會拿到什麼結果」。
這是許多 Docstring 常被忽略的超級關鍵。
再論舉例的重要性
上述例子的 output 其實算是很單純的。
很多時候,函式的 output 可能是一個字典,究竟有哪些 key、value?一言難盡!
這種情況的舉例就會變得特別重要:
1 | def get_user_summary(user_id: str) -> dict: |
作為函式的調用方,我常常需要直接使用回傳值裡面的元素,所以我希望能一眼就看出這個 output 到底是什麼、長得什麼樣——舉例真的很重要。
在我看來,舉例幾乎是最關鍵的 context 補充方式。
結語:寫給「下一個開發者」的情書
Docstring 就像一封情書,寫給未來的自己、同事或其他開發者。
我打從心底認為,Docstring 有著十分務實的一面。
從本文的例子中,你應該能夠感受到在有和沒有 Docstring 的情況下,理解這些程式碼所需要的時間差距與認知負擔。
而這正是 Docstring 的價值所在。