Python 單元測試與除錯:深入使用 pdb 除錯器
簡介
在開發 Python 程式時,除錯是不可或缺的環節。即使是最簡單的函式,也可能因為資料類型、邏輯錯誤或外部資源的變化而產生不預期的行為。傳統上,我們常透過 print() 觀察變數值來定位問題,但這種方式效率低下且難以追蹤程式執行流程。
pdb(Python Debugger)是 Python 內建的互動式除錯工具,提供斷點、單步執行、檢視堆疊與變數等功能,讓開發者能在程式執行時即時檢查狀態。相較於 print(),pdb 讓除錯變得系統化、可重現且更具可讀性,尤其在測試驅動開發(TDD)或排除複雜邏輯錯誤時,顯得格外有價值。
本篇文章將從 pdb 的基本操作、常用指令、實務範例,到 常見陷阱與最佳實踐,一步步帶領讀者掌握這個強大的除錯利器,適合剛入門的初學者以及想提升除錯效率的中級開發者。
核心概念
1. 為什麼選擇 pdb?
- 內建且跨平台:不需要額外安裝套件,Python 標準庫即提供。
- 互動式 REPL:在程式執行時即能輸入 Python 表達式,檢查變數或呼叫函式。
- 支援斷點與條件斷點:可於任意行設置斷點,甚至在特定條件成立時才停下。
- 與單元測試整合:在
unittest、pytest中直接呼叫 pdb,快速定位失敗測試。
2. 基本啟動方式
| 方式 | 說明 |
|---|---|
python -m pdb your_script.py |
直接以模組方式啟動,程式會在第一行暫停。 |
import pdb; pdb.set_trace() |
在程式碼任意位置插入斷點,執行到此行時會進入 pdb。 |
pdb.run('your_function()') |
以程式字串方式執行,適合在交互式環境測試。 |
小技巧:在 VS Code、PyCharm 等 IDE 中,亦可設定斷點,背後其實都是呼叫
pdb(或類似的除錯協定)實作。
3. 常用指令總覽
| 指令 | 功能 |
|---|---|
l (list) |
顯示目前程式碼片段,預設前後 11 行。 |
n (next) |
執行當前行,停在下一行(不會進入函式內部)。 |
s (step) |
進入函式執行,適合想觀察函式內部流程時使用。 |
c (continue) |
繼續執行至下一個斷點或程式結束。 |
b [lineno] |
在指定行號設置斷點。 |
b filename:lineno |
在其他檔案的特定行設斷點。 |
b 12 if x > 5 |
條件斷點:當 x > 5 時才停下。 |
p expression |
列印 表達式結果,等同於 print()。 |
pp expression |
以「pretty‑print」方式顯示,對大型結構更友好。 |
w (where) |
顯示目前呼叫堆疊(stack trace)。 |
q (quit) |
結束除錯會話,程式直接退出。 |
註:所有指令皆可使用完整字母(如
list、next)或縮寫(l、n)呼叫,對於習慣鍵盤操作的開發者非常便利。
程式碼範例
以下示範 5 個實用的 pdb 範例,涵蓋斷點設定、條件斷點、檢視變數與堆疊、以及與 unittest 的結合。
範例 1:最簡單的斷點 set_trace()
def fibonacci(n: int) -> int:
"""回傳第 n 個費波那契數字(從 0 開始計算)"""
a, b = 0, 1
for i in range(n):
a, b = b, a + b
return a
if __name__ == "__main__":
import pdb; pdb.set_trace() # <-- 程式執行到此會暫停
result = fibonacci(10)
print(f"第 10 個費波那契數字是 {result}")
執行後會進入
pdb,此時可使用p a, b觀察迴圈變數,或n逐行執行,確認演算法是否正確。
範例 2:條件斷點與 list 檢視
def find_target(nums, target):
for idx, val in enumerate(nums):
if val == target:
return idx
return -1
data = [3, 7, 2, 9, 5, 2, 8]
target = 2
# 直接在程式外使用 pdb,設定條件斷點
# $ python -m pdb demo2.py
# (Pdb) b demo2.py:6 if val == target
# (Pdb) c
b demo2.py:6 if val == target只在val等於target時停下,省去不必要的步進。- 使用
l可一次看到第 4~10 行程式碼,快速定位斷點所在。
範例 3:檢查大型資料結構
def process_data(data):
# 假設 data 為嵌套字典
total = 0
for k, v in data.items():
total += sum(v["values"])
return total
sample = {
"a": {"values": [1, 2, 3]},
"b": {"values": [4, 5, 6]},
"c": {"values": [7, 8, 9]},
}
if __name__ == "__main__":
import pdb; pdb.set_trace()
print(process_data(sample))
在 pdb 內使用 pp data(或 pprint)可以整齊呈現嵌套結構,幫助快速找出錯誤的鍵或值。
範例 4:與 unittest 結合
import unittest
def divide(a, b):
return a / b
class TestMath(unittest.TestCase):
def test_divide(self):
self.assertEqual(divide(10, 2), 5)
# 故意觸發錯誤,除以 0
self.assertRaises(ZeroDivisionError, divide, 10, 0)
if __name__ == "__main__":
# 在測試失敗時自動啟動 pdb
unittest.main(buffer=True, failfast=False, catchbreak=True)
執行 python -m unittest -v test_demo.py,若測試失敗,unittest 會拋出例外,我們可以在例外處加上 import pdb; pdb.set_trace(),或直接使用 --pdb 參數(pytest 支援):
$ pytest --pdb test_demo.py
測試失敗瞬間即進入 pdb,讓我們立即檢查失敗的上下文。
範例 5:遠端除錯(在 Docker 容器中)
# Dockerfile 範例
FROM python:3.11-slim
WORKDIR /app
COPY . /app
RUN pip install -r requirements.txt
CMD ["python", "-m", "pdb", "app.py"]
- 只要在容器啟動時使用
-m pdb,即使程式在容器內執行,也能透過docker attach直接與 pdb 互動。 - 這對於微服務或CI/CD 流程中,快速定位跑在容器內的錯誤非常有幫助。
常見陷阱與最佳實踐
| 陷阱 | 說明 | 解決方案 |
|---|---|---|
忘記移除 set_trace() |
在正式部署前遺漏除錯斷點,會導致程式卡住或泄漏資訊。 | 使用 lint 工具(如 flake8、pylint)檢查 pdb.set_trace(),或在 CI 中加入 grep -R "set_trace"。 |
| 條件斷點過於複雜 | 條件表達式若含大量運算,會拖慢除錯速度。 | 盡量將條件抽離成簡單變數,或在斷點前先 p 印出關鍵變數,判斷是否需要設定條件斷點。 |
未使用 continue |
只靠 n/s 逐行走,會浪費時間。 |
先用 c 直接跑到下個斷點,再根據需要 s 進入函式。 |
| 在多執行緒/多程序環境下使用 pdb | pdb 只支援單執行緒,其他線程會被阻塞。 | 使用 logging 或 remote debugging(如 pydevd)取代;若必須除錯,先暫停其他線程。 |
| 忽視堆疊資訊 | 只看變數而不檢視呼叫堆疊,容易錯過上下文。 | 常使用 w(where)或 bt(在 ipdb 中)查看堆疊,確保知道程式是從哪裡呼叫進來的。 |
最佳實踐:
- 斷點最小化:只在懷疑有問題的程式碼行設斷點,避免整段程式被暫停。
- 使用
pp:對於列表、字典等結構,pp能讓輸出更易讀。 - 結合
logging:在正式環境保留logging,在開發階段以pdb快速定位。 - 自動化測試:在
pytest中加入--pdb,讓失敗測試自動進入除錯。 - 清理除錯代碼:提交前務必移除所有
pdb.set_trace(),使用 pre‑commit hook 來防止遺漏。
實際應用場景
| 場景 | 為何適合使用 pdb |
|---|---|
| 演算法錯誤(如排序、遞迴) | 可逐層觀察遞迴呼叫堆疊,快速發現遞迴深度或基礎條件錯誤。 |
| 資料清理腳本(大量 CSV/JSON) | 在處理特定行或欄位時使用條件斷點,避免每筆資料都停下。 |
| API 服務(Flask/Django) | 在路由函式內插入 set_trace(),即時檢查 request 參數與回傳值。 |
| 多模組專案 | 透過 b filename:lineno 在不同檔案設斷點,免於在主程式中加入大量 import pdb。 |
| CI/CD 故障排除 | 當測試在 CI 環境失敗,加入 --pdb 讓 CI 直接停在錯誤點,產生除錯日誌供後續分析。 |
總結
pdb是 Python 內建、功能完整且跨平台 的除錯工具,能在程式執行時即時檢查變數、堆疊與程式流程。- 透過 斷點、條件斷點、單步執行與 pretty‑print,開發者可以大幅提升除錯效率,減少依賴
print()的雜訊。 - 在 單元測試、資料處理、API 開發與容器化部署 等多種情境下,pdb 都能提供即時且精確的資訊,協助快速定位問題。
- 為避免遺漏除錯程式碼或造成效能問題,請遵守 最佳實踐:斷點最小化、使用
pp、結合 logging、在 CI 中自動化除錯、並在提交前清理set_trace()。
掌握 pdb 後,你將不再對「程式跑錯」感到手足無措,而是能像偵探般逐步追蹤、精準定位,讓 Python 開發變得更順手、更可靠。祝你除錯順利,寫出更穩定的程式!