Python 課程 – 函式(Functions)
主題:函式註解(docstring)
簡介
在 Python 中,函式是程式的基本組件,負責把邏輯切割成可重複使用的單位。而要讓這些函式在團隊合作、文件生成或除錯時保持可讀性與可維護性,**函式註解(docstring)**是不可或缺的工具。
docstring 不只是簡單的說明文字,它會被 Python 直譯器保存於函式物件的 __doc__ 屬性中,讓 IDE、help()、Sphinx 等文件產生工具都能自動取得說明內容。對於 初學者 來說,寫好 docstring 能培養良好的程式碼風格;對 中級開發者,則是建立大型專案、API 文件的基礎。
本篇文章將從 docstring 的語法、常見寫法、實務範例,一路探討到 常見陷阱 以及 最佳實踐,最後提供 實際應用場景,幫助你在寫程式時就能自然而然地加入完整且易讀的說明。
核心概念
1. 何謂 docstring?
docstring(documentation string)是放在函式、類別或模組最前面的 字串常值,使用三個單引號(''')或三個雙引號(""")包住。它會在程式執行時被 Python 讀取,並存入物件的 __doc__ 屬性。
def add(a, b):
"""回傳 a 與 b 的和。"""
return a + b
print(add.__doc__) # 輸出: 回傳 a 與 b 的和。
注意:docstring 必須是函式的第一條語句,否則 Python 不會將它視為說明文字。
2. 常見的 docstring 風格
| 風格 | 特色 | 典型使用情境 |
|---|---|---|
| PEP 257 | 官方推薦的簡潔寫法,適合小型專案 | 基礎教學、腳本 |
| Google style | 標題式分段,易於閱讀,支援自動化工具(如 Sphinx + napoleon) | 中大型專案、API 文件 |
| NumPy/SciPy style | 強調參數、回傳值與範例的詳細說明,常見於科學計算套件 | 數值或資料科學套件 |
以下示範 Google style 與 NumPy style 的差異:
def divide(dividend, divisor):
"""執行除法運算,並回傳結果。
Args:
dividend (float): 被除數。
divisor (float): 除數,若為 0 會拋出 ZeroDivisionError。
Returns:
float: 除法結果。
Raises:
ZeroDivisionError: 當 divisor 為 0 時。
"""
if divisor == 0:
raise ZeroDivisionError("除數不可為 0")
return dividend / divisor
def divide(dividend, divisor):
"""
執行除法運算,並回傳結果。
Parameters
----------
dividend : float
被除數。
divisor : float
除數,若為 0 會拋出 ZeroDivisionError。
Returns
-------
float
除法結果。
Raises
------
ZeroDivisionError
當 divisor 為 0 時。
"""
if divisor == 0:
raise ZeroDivisionError("除數不可為 0")
return dividend / divisor
3. 為什麼要寫 docstring?
- 自動化文件產生:
pydoc, Sphinx, MkDocs 等工具會直接解析__doc__,產出 HTML、PDF 等文件。 - IDE 支援:大多數 IDE(如 VS Code、PyCharm)會在呼叫函式時顯示 docstring,減少切換文件查找說明的時間。
- 除錯與測試:
help()與inspect.getdoc()能即時檢視說明,對於 REPL 開發非常有幫助。 - 團隊協作:統一的說明格式讓新成員快速了解函式的行為、參數型別與例外情形。
程式碼範例
以下提供 5 個實用範例,示範不同情境下的 docstring 寫法與說明。
1️⃣ 基本範例 – 單行說明
def greet(name):
"""回傳問候語句,例如:'Hello, Alice!'。"""
return f"Hello, {name}!"
- 說明:只需要一行簡短描述時,直接放在第一行即可。
- 適用:小工具、一次性腳本。
2️⃣ 多行說明 – 包含參數與回傳值
def factorial(n):
"""計算正整數 n 的階乘。
Args:
n (int): 必須是非負整數。
Returns:
int: n 的階乘值。
Raises:
ValueError: 當 n 為負數時拋出。
"""
if n < 0:
raise ValueError("n 必須是非負整數")
result = 1
for i in range(2, n + 1):
result *= i
return result
- 說明:使用 Google style,清楚列出 Args、Returns、Raises。
- 適用:函式邏輯較複雜、需要說明例外。
3️⃣ 範例程式碼 – 讓使用者快速上手
def is_palindrome(s):
"""判斷字串 s 是否為回文。
會忽略大小寫與非字母數字字符。
Args:
s (str): 任意字串。
Returns:
bool: 若 s 為回文回傳 True,否則 False。
Example:
>>> is_palindrome("A man, a plan, a canal: Panama")
True
"""
import re
cleaned = re.sub(r'[\W_]', '', s).lower()
return cleaned == cleaned[::-1]
- 說明:加入 Example 區段,讓
doctest能直接驗證。 - 適用:演算法或需示範呼叫方式的函式。
4️⃣ 多重回傳值與型別說明(NumPy style)
def split_name(full_name):
"""
將全名切分為姓與名。
Parameters
----------
full_name : str
形如 "姓 名" 或 "姓 中間名 名" 的字串。
Returns
-------
tuple
(family_name, given_name) 兩個字串。
Notes
-----
若字串中僅有一個空格,視為「姓」與「名」;若有多個空格,
只保留第一個空格前的文字為姓,其餘全部視為名。
"""
parts = full_name.strip().split()
if len(parts) == 1:
return parts[0], ''
family = parts[0]
given = ' '.join(parts[1:])
return family, given
- 說明:使用 NumPy style,適合需要「Notes」或「See Also」等額外說明的情況。
- 適用:資料處理、科學計算相關函式。
5️⃣ 裝飾器(Decorator)中的 docstring 繼承
import functools
def preserve_docstring(func):
"""裝飾器:保留被裝飾函式的 docstring。"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
"""wrapper 只負責呼叫原始函式並回傳結果。"""
return func(*args, **kwargs)
return wrapper
@preserve_docstring
def multiply(a, b):
"""回傳 a 與 b 的乘積。"""
return a * b
print(multiply.__doc__) # 輸出: 回傳 a 與 b 的乘積。
- 說明:使用
functools.wraps能自動把原函式的__doc__複製到 wrapper,避免文件遺失。 - 適用:開發框架或共用裝飾器時,務必保留原始說明。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方式 |
|---|---|---|
| 未將 docstring 放在第一行 | Python 只會把緊接在 def 後的第一個字串視為 docstring。 |
確保 """說明""" 緊貼在函式定義之後,且不要在前面寫任何其他語句(包括註解)。 |
| 使用單行字串卻未加三引號 | 單行字串若使用單引號 '說明',會被視為普通字面值,不會存入 __doc__。 |
使用三引號 """說明""" 或 '''說明'''。 |
| docstring 與程式碼行寬不一致 | 長段落若直接寫在一行,會超過 79 個字符,違反 PEP 8。 | 換行分段,或使用自動換行工具(如 black)保持 72 個字符寬度。 |
| 忽略例外說明 | 呼叫者可能不知道哪些例外會被拋出,導致錯誤處理不完整。 | 在 docstring 中加入 Raises 或 Exceptions 區段。 |
| 裝飾器遺失原始 docstring | 裝飾器未使用 functools.wraps 時,__doc__ 會被裝飾器的說明取代。 |
一律在自訂裝飾器內使用 @functools.wraps(original_func)。 |
| 混用不同風格 | 同一專案內部風格不統一,會影響自動文件產生的結果。 | 先訂下團隊共識(PEP 257、Google、NumPy),使用 linters(如 pylint、flake8-docstrings)檢查。 |
最佳實踐總結:
- 統一風格:依團隊或專案選擇 PEP 257、Google 或 NumPy,並在
pyproject.toml中設定 linter。 - 完整描述:至少包含 說明、參數、回傳值,如有例外或特殊行為則補上 Raises、Notes。
- 使用
functools.wraps:自訂裝飾器時一定要保留原始 docstring。 - 加入範例(Example):讓使用者能直接看到呼叫方式,並方便
doctest測試。 - 保持可讀性:每行不超過 72 個字符,段落之間空一行,使用縮排與標題提升結構清晰度。
實際應用場景
1. 企業內部 API 文件
公司內部提供的 Python SDK(例如資料庫、雲端服務)需要自動產生線上文件。只要每個公開函式遵守 Google style,使用 Sphinx + Napoleon 擴充即可在 CI 流程中自動產出 HTML 手冊,開發者只要點擊 help(module.function) 或在 IDE 中懸停即可看到完整說明。
2. 開源套件的貢獻流程
在開源專案(例如 pandas、requests)中,貢獻者提交 Pull Request 時,CI 會跑 flake8-docstrings 檢查 docstring 是否缺失或不符合規範。若缺少說明或未列出例外,PR 會被拒絕,確保文件與程式碼同步。
3. 教學或科研程式碼
在 Jupyter Notebook 中,使用 ?function_name 直接呼叫 docstring,能即時顯示說明,對於教學或科研筆記非常便利。加入 Example 區段後,doctest 甚至可以自動驗證示範程式碼是否仍然正確。
4. 自動化測試與偵錯
測試框架(如 pytest)結合 pytest-doctest,會自動執行 docstring 中的範例,確保說明與實作保持一致。這對於函式的邊界條件(例如除以 0)特別有幫助。
總結
函式註解(docstring) 不只是寫給別人的說明,更是 程式碼自我描述、文件自動化、除錯與測試 的核心資產。從 單行簡述、多行參數說明、範例程式碼 到 裝飾器的繼承,掌握正確的語法與風格,能讓你的 Python 專案在團隊合作與開源貢獻時保持高可讀性與可維護性。
記住:
- 第一行必須是 docstring,使用三引號包住。
- 選擇並統一風格(PEP 257 / Google / NumPy)。
- 完整描述參數、回傳值與可能的例外。
- 加入範例,讓使用者一眼就能看懂如何呼叫。
- 使用
functools.wraps保留裝飾器的說明。
只要在寫每一個函式時都養成加入清晰 docstring 的習慣,未來的除錯、文件產生與團隊協作都會變得更加順暢。祝你在 Python 的旅程中,寫出既功能完整又說明清楚的程式碼!