本文 AI 產出,尚未審核

Python 單元測試與除錯:深入使用 pdb 除錯器


簡介

在開發 Python 程式時,除錯是不可或缺的環節。即使是最簡單的函式,也可能因為資料類型、邏輯錯誤或外部資源的變化而產生不預期的行為。傳統上,我們常透過 print() 觀察變數值來定位問題,但這種方式效率低下且難以追蹤程式執行流程

pdb(Python Debugger)是 Python 內建的互動式除錯工具,提供斷點、單步執行、檢視堆疊與變數等功能,讓開發者能在程式執行時即時檢查狀態。相較於 print()pdb 讓除錯變得系統化、可重現且更具可讀性,尤其在測試驅動開發(TDD)或排除複雜邏輯錯誤時,顯得格外有價值。

本篇文章將從 pdb 的基本操作常用指令實務範例,到 常見陷阱與最佳實踐,一步步帶領讀者掌握這個強大的除錯利器,適合剛入門的初學者以及想提升除錯效率的中級開發者。


核心概念

1. 為什麼選擇 pdb?

  • 內建且跨平台:不需要額外安裝套件,Python 標準庫即提供。
  • 互動式 REPL:在程式執行時即能輸入 Python 表達式,檢查變數或呼叫函式。
  • 支援斷點與條件斷點:可於任意行設置斷點,甚至在特定條件成立時才停下。
  • 與單元測試整合:在 unittestpytest 中直接呼叫 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) 結束除錯會話,程式直接退出。

:所有指令皆可使用完整字母(如 listnext)或縮寫(ln)呼叫,對於習慣鍵盤操作的開發者非常便利。


程式碼範例

以下示範 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 工具(如 flake8pylint)檢查 pdb.set_trace(),或在 CI 中加入 grep -R "set_trace"
條件斷點過於複雜 條件表達式若含大量運算,會拖慢除錯速度。 盡量將條件抽離成簡單變數,或在斷點前先 p 印出關鍵變數,判斷是否需要設定條件斷點。
未使用 continue 只靠 n/s 逐行走,會浪費時間。 先用 c 直接跑到下個斷點,再根據需要 s 進入函式。
在多執行緒/多程序環境下使用 pdb pdb 只支援單執行緒,其他線程會被阻塞。 使用 loggingremote debugging(如 pydevd)取代;若必須除錯,先暫停其他線程。
忽視堆疊資訊 只看變數而不檢視呼叫堆疊,容易錯過上下文。 常使用 w(where)或 bt(在 ipdb 中)查看堆疊,確保知道程式是從哪裡呼叫進來的。

最佳實踐

  1. 斷點最小化:只在懷疑有問題的程式碼行設斷點,避免整段程式被暫停。
  2. 使用 pp:對於列表、字典等結構,pp 能讓輸出更易讀。
  3. 結合 logging:在正式環境保留 logging,在開發階段以 pdb 快速定位。
  4. 自動化測試:在 pytest 中加入 --pdb,讓失敗測試自動進入除錯。
  5. 清理除錯代碼:提交前務必移除所有 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 直接停在錯誤點,產生除錯日誌供後續分析。

總結

  • pdbPython 內建、功能完整且跨平台 的除錯工具,能在程式執行時即時檢查變數、堆疊與程式流程。
  • 透過 斷點、條件斷點、單步執行與 pretty‑print,開發者可以大幅提升除錯效率,減少依賴 print() 的雜訊。
  • 單元測試、資料處理、API 開發與容器化部署 等多種情境下,pdb 都能提供即時且精確的資訊,協助快速定位問題。
  • 為避免遺漏除錯程式碼或造成效能問題,請遵守 最佳實踐:斷點最小化、使用 pp、結合 logging、在 CI 中自動化除錯、並在提交前清理 set_trace()

掌握 pdb 後,你將不再對「程式跑錯」感到手足無措,而是能像偵探般逐步追蹤、精準定位,讓 Python 開發變得更順手、更可靠。祝你除錯順利,寫出更穩定的程式!