pdb --- Python 偵錯器

原始碼:Lib/pdb.py


pdb 模組定義了一個 Python 程式的互動式原始碼偵錯器。它支援在原始碼列層級 (source line level) 設定(條件式的)斷點 (breakpoint) 和單步執行、檢視 stack frame(堆疊框)、列出原始碼、以及在任何 stack frame 情境 (context) 中為任意 Python 程式碼求值 (evaluation)。它還支援事後偵錯 (post-mortem debugging),並可以在程式控制下呼叫。

偵錯器是可擴充的 —— 偵錯器實際被定義為 Pdb 類別。該類別目前沒有文件,但可以很容易地透過閱讀原始碼來理解它。擴充套件介面使用了 bdbcmd 模組。

也參考

faulthandler 模組

用於在出現故障時、超時 (timeout) 後或於接收到使用者訊號時,顯式地轉儲 (dump) Python 回溯 (traceback)。

traceback 模組

用於提取、格式化和印出 Python 程式 stack trace(堆疊追蹤)的標準介面。

自一個執行中程式切入偵錯器的典型用法為插入:

import pdb; pdb.set_trace()

或:

breakpoint()

到你想切入偵錯器的位置,然後執行程式,就可以單步執行上述陳述式之後的程式碼,並可以使用 continue 命令來離開偵錯器、繼續執行。

在 3.7 版的變更: 當使用預設值呼叫時,可以使用內建的 breakpoint() 來取代 import pdb; pdb.set_trace()

def double(x):
   breakpoint()
   return x * 2
val = 3
print(f"{val} * 2 is {double(val)}")

偵錯器的提示字元是 (Pdb),這表示你處於偵錯模式:

> ...(2)double()
-> breakpoint()
(Pdb) p x
3
(Pdb) continue
3 * 2 is 6

在 3.3 版的變更: 透過 readline 模組達成的 tab 補全可用於補全本模組的命令和命令的引數,例如會提供當前的全域和區域名稱以作為 p 命令的引數。

你還可以從命令列調用 pdb 來偵錯其他腳本。例如:

python -m pdb myscript.py

當作為模組調用時,如果被偵錯的程序不正常地退出,pdb 將自動進入事後偵錯。事後偵錯後(或程式正常退出後),pdb 將重新啟動程式。自動重新啟動會保留 pdb 的狀態(例如斷點),並且在大多數情況下比在程式退出時退出偵錯器更有用。

在 3.2 版的變更: 新增了 -c 選項來執行命令,就像能在 .pdbrc 檔案中給定的那樣;請參閱偵錯器命令

在 3.7 版的變更: 新增了 -m 選項以類似於 python -m 的方式來執行模組。與腳本一樣,偵錯器將在模組的第一列之前暫停執行。

在偵錯器控制下執行陳述式的典型用法是:

>>> import pdb
>>> def f(x):
...     print(1 / x)
>>> pdb.run("f(2)")
> <string>(1)<module>()
(Pdb) continue
0.5
>>>

檢查一個損壞程式的典型用法:

>>> import pdb
>>> def f(x):
...     print(1 / x)
...
>>> f(0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
ZeroDivisionError: division by zero
>>> pdb.pm()
> <stdin>(2)f()
(Pdb) p x
0
(Pdb)

在 3.13 版的變更: PEP 667 的實作意味著透過 pdb 進行的名稱賦予 (name assignments) 會立即影響有效作用域,即使在最佳化作用域內運作也是如此。

本模組定義了下列函式,每個函式進入偵錯器的方式略有不同:

pdb.run(statement, globals=None, locals=None)

在偵錯器控制下執行 statement(以字串或程式碼物件形式給定)。偵錯提示字元會在執行任何程式碼前出現;你可以設定斷點並輸入 continue,或也可以使用 stepnext 逐步執行陳述式(這些命令在下面都有說明)。可選引數 globalslocals 指定程式碼執行的環境;預設使用 __main__ 模組的字典。(請參閱內建函式 exec()eval() 的說明。)

pdb.runeval(expression, globals=None, locals=None)

在偵錯器控制下為 expression 求值(以字串或程式碼物件形式給定)。當 runeval() 回傳時,它回傳 expression 的值。除此之外,該函式與 run() 類似。

pdb.runcall(function, *args, **kwds)

使用給定的引數呼叫 function(只可以是函式或方法物件,不能是字串)。runcall() 回傳的是所呼叫函式的回傳值。偵錯器提示字元將在進入函式後立即出現。

pdb.set_trace(*, header=None)

在呼叫此函式的 stack frame 進入偵錯器。用於在程式中給定之處寫死 (hard-code) 一個斷點,即便該程式碼不在偵錯狀態(如斷言失敗時)。如有給定 header,它將在偵錯正要開始前被印出到控制台。

在 3.7 版的變更: 僅限關鍵字引數 header

在 3.13 版的變更: set_trace() 將立即進入偵錯器,而不是在下一列要執行的程式碼中。

pdb.post_mortem(traceback=None)

進入所給定 traceback 物件的事後偵錯。如果沒有給定 traceback,預設使用當前正在處理的例外之一(使用預設情況時,必須要有正在處理的例外存在)。

pdb.pm()

進入在 sys.last_exc 中發現的例外的事後偵錯。

run* 函式和 set_trace() 都是別名,用於實例化 (instantiate) Pdb 類別並呼叫同名方法。如果要使用更多功能,則必須自己執行以下操作:

class pdb.Pdb(completekey='tab', stdin=None, stdout=None, skip=None, nosigint=False, readrc=True)

Pdb 是偵錯器類別。

completekeystdinstdout 引數會被傳到底層的 cmd.Cmd 類別;請於該文件閱讀相關敘述。

如果給定 skip 引數,則它必須是一個給出 glob 樣式之模組名稱的疊代器。如果遇到匹配這些樣式的模組,偵錯器將不會進入來自該模組的 frame。 [1]

預設情況下,當你發出 continue 命令時,Pdb 會為 SIGINT 訊號(即使用者在控制台上按下 Ctrl-C 時會發送的訊號)設定一個處理程式 (handler),這允許你透過按下 Ctrl-C 再次切入偵錯器。如果你希望 Pdb 不影響到 SIGINT 處理程式,請將 nosigint 設定為 true。

readrc 引數預設為 true,它控制 Pdb 是否從檔案系統載入 .pdbrc 檔案。

啟用追蹤 (tracing) 且帶有 skip 引數的呼叫示範:

import pdb; pdb.Pdb(skip=['django.*']).set_trace()

不帶引數地引發一個稽核事件 (auditing event) pdb.Pdb

在 3.1 版的變更: 新增了 skip 參數。

在 3.2 版的變更: 新增了 nosigint 參數。以前 SIGINT 處理程式從未被 Pdb 設定過。

在 3.6 版的變更: readrc 引數。

run(statement, globals=None, locals=None)
runeval(expression, globals=None, locals=None)
runcall(function, *args, **kwds)
set_trace()

請見上面關於這些函式的文件說明。

偵錯器命令

下方列出的是偵錯器能認得的命令。如下所示,大多數命令可以縮寫為一個或兩個字母。如 h(elp) 表示可以輸入 hhelp 來輸入幫助命令(但不能輸入 hehel,也不能是 HHelpHELP)。命令的引數必須用空格(空格符 (spaces) 或製表符 (tabs))分隔。在命令語法中,可選引數被括在方括號 ([]) 中;使用時請勿輸入方括號。命令語法中的選擇項由豎線 (|) 分隔。

輸入一個空白列 (blank line) 將重複上次輸入的命令。例外:如果上一個命令是 list 命令,則會列出接下來的 11 列。

偵錯器無法識別的命令將被認為是 Python 陳述式,並以正在偵錯的程式之情境來執行。Python 陳述式也可以用驚嘆號 (!) 作為前綴,這是檢視正在偵錯之程式的強大方法,甚至可以修改變數或呼叫函式。當此類陳述式發生例外,將印出例外名稱,但偵錯器的狀態不會改變。

在 3.13 版的變更: 現在可以正確辨識並執行前綴為 pdb 命令的運算式/陳述式。

偵錯器有支援設定別名。別名可以有參數,使得偵錯器對被檢查的情境有一定程度的適應性。

在一列中可以輸入多個以 ;; 分隔的命令。(不能使用單個 ;,因為它用於分隔傳遞給 Python 剖析器一列中的多個命令。)切分命令沒運用什麼高深的方式;輸入總是在第一處 ;; 被切分開,即使它位於引號內的字串之中。對於具有雙分號字串的一個變通解法,是使用隱式字串連接 ';'';'";"";"

要設定臨時全域變數,請使用便利變數 (convenience variable)便利變數是名稱以 $ 開頭的變數。例如 $foo = 1 會設定一個全域變數 $foo,你可以在偵錯器會話 (debugger session) 中使用它。當程式恢復執行時,便利變數將被清除,因此與使用 foo = 1 等普通變數相比,它不太會去干擾你的程式。

共有三個預先設定的便利變數

  • $_frame:當前正在偵錯的 frame

  • $_retval:frame 回傳時的回傳值

  • $_exception:frame 引發例外時的例外

在 3.12 版被加入: 新增了便利變數功能。

如果 .pdbrc 檔案存在於使用者的家目錄或當前目錄中,則會使用 'utf-8' 編碼讀取並執行該檔案,就像在偵錯器提示字元下鍵入該檔案一樣,除了空列和以 # 開頭的列會被忽略之外。這對於別名設定特別有用。如果兩個檔案都存在,則先讀取家目錄中的檔案,且定義於其中的別名可以被本地檔案覆蓋。

在 3.2 版的變更: .pdbrc 現在可以包含繼續偵錯的命令,如 continuenext。以前檔案中的這些命令是無效的。

在 3.11 版的變更: .pdbrc 現在使用 'utf-8' 編碼讀取。以前它是使用系統區域設定編碼讀取的。

h(elp) [command]

如不帶引數,印出可用的命令列表。引數為 command 時,印出有關該命令的幫助訊息,help pdb 會顯示完整文件(即 pdb 模組的說明字串 (docstring))。由於 command 引數必須是一個識別字 (identifier),若要獲取 ! 命令的幫助訊息則必須輸入 help exec

w(here)

印出 stack trace,最新的 frame 會位於底部。箭頭(>)表示當前的 frame,它也決定了大多數命令的情境。

d(own) [count]

在 stack trace 中,將當前 frame 向下移動 count 級(預設為 1 級,移往較新的 frame)。

u(p) [count]

在 stack trace 中,將當前 frame 向上移動 count 級(預設為 1 級,移向較舊的 frame)。

b(reak) [([filename:]lineno | function) [, condition]]

如帶有 lineno 引數,則在目前檔案中的 lineno 列處設定中斷。列號可以以 filename 和冒號為前綴,以指定另一個檔案(可能是尚未載入的檔案)中的斷點。該檔案會在 sys.path 上搜尋。可接受的 filename 形式為 /abspath/to/file.pyrelpath/file.pymodulepackage.module

如帶有 function 引數,在該函式內的第一個可執行陳述式處設定中斷。function 可以是任何其求值結果為目前命名空間中函式的運算式。

如果第二個引數存在,它是一個運算式,在斷點生效前其必須求值為 true

如果不帶引數執行會列出所有斷點資訊,包括每個斷點、命中該斷點的次數、當前的忽略次數以及關聯的條件(如存在)。

每個斷點都有賦予一個編號,所有其他斷點命令都參照該編號。

tbreak [([filename:]lineno | function) [, condition]]

臨時斷點,在第一次遇見時會自動被刪除。它的引數與 break 相同。

cl(ear) [filename:lineno | bpnumber ...]

如帶有 filename:lineno 引數,則清除此列上的所有斷點。如果引數是空格分隔的斷點編號列表,則清除這些斷點。如果不帶引數則清除所有斷點(但會先提示確認)。

disable bpnumber [bpnumber ...]

停用斷點,斷點以空格分隔的斷點編號列表來給定。停用斷點表示它不會導致程式停止執行,但是與清除斷點不同,停用的斷點將保留在斷點列表中並且可以(重新)啟用。

enable bpnumber [bpnumber ...]

啟用指定的斷點。

ignore bpnumber [count]

為給定的斷點編號設定忽略計數。如果省略 count 則忽略計數設定為 0。當忽略計數為 0 時,斷點將變為有效狀態。當非 0 時,每次到達斷點,且斷點沒有被禁用,且任何關聯的條件被求值為 true,則 count 就會遞減。

condition bpnumber [condition]

為斷點設定一個新 condition,為一個運算式,且其求值結果為 true 時斷點才會起作用。如果沒有給定 condition,則刪除任何現有條件,也就是不為斷點設定條件。

commands [bpnumber]

為編號是 bpnumber 的斷點指定一系列命令。命令內容出現在後續的幾列中。輸入僅包含 end 的一列來結束命令列表。例如:

(Pdb) commands 1
(com) p some_variable
(com) end
(Pdb)

要刪除斷點上的所有命令,請輸入 commands 並立即以 end 結尾,也就是不指定任何命令。

不帶有 bpnumber 引數則 commands 會關聯到上一個設定的斷點。

可以使用斷點命令來重新啟動程式,只需使用 continuestep 命令,或其他可以繼續執行程式的命令。

如果指定了某個繼續執行程式的命令(目前包括 continuestepnextreturnjumpquit 及它們的縮寫)將終止命令列表(就像該命令後馬上跟著 end)。因為在任何時候繼續執行下去(即使是簡單的 next 或 step),都可能會遇到另一個斷點,該斷點可能具有自己的命令列表,這會導致無法確定要執行哪個列表。

如果你在命令列表中使用 silent 命令,則平常會有的那些關於停止於斷點處的訊息就不會印出。對於要印出特定訊息再繼續的斷點來說,這可能會是需要的功能。如果其他命令都沒有印出任何內容,那你就看不到已到達斷點的跡象。

s(tep)

執行當前列,在第一個可以停止的位置(在被呼叫的函式內部或在當前函式的下一列)停止。

n(ext)

繼續執行,直至執行到當前函式的下一列或者當前函式回傳為止。(nextstep 之間的區別在於 step 會在被呼叫函式的內部停止、而 next(幾乎)全速執行被呼叫的函式,並僅在當前函式的下一列停止。)

unt(il) [lineno]

如果不帶引數則繼續執行,直到列號比當前的列大時停止。

如帶有 lineno 則繼續執行,直到到達列號大於或等於 lineno 的那一列。在這兩種情況下,當前 frame 回傳時也會停止。

在 3.2 版的變更: 允許明確給定一個列號。

r(eturn)

繼續執行,直到目前的函式回傳。

c(ont(inue))

繼續執行,除非遇到斷點才停下來。

j(ump) lineno

設定即將執行的下一列,僅可用於堆疊中最底部的 frame。這讓你可以跳回去並再次執行程式碼,或者往前跳以跳過不想執行的程式碼。

需要注意的是,不是所有的跳轉都是被允許的 -- 例如不能跳轉到 for 迴圈的中間或跳出 finally 子句。

l(ist) [first[, last]]

列出當前檔案的原始碼。如果不帶引數,則列出當前列周圍的 11 列,或繼續先前的列表操作。如果用 . 作為引數,則列出當前列周圍的 11 列。如果帶有一個引數,則列出那一列周圍的 11 列。如果帶有兩個引數,則列出給定範圍中的程式碼;如果第二個引數小於第一個引數,則將其直譯為要列出的列數。

當前 frame 中的當前列會用 -> 標記出來。如果正在偵錯一個例外,且引發或傳遞該例外的那一列不是當前列,則會用 >> 來標記該列。

在 3.2 版的變更: 新增了 >> 標記。

ll | longlist

列出當前函式或 frame 的所有原始碼。相關列的標記方式與 list 相同。

在 3.2 版被加入.

a(rgs)

印出當前函式的引數及它們當前的值。

p expression

在當前情境中為 expression 求值並印出其值。

備註

也可以使用 print(),但它不是一個偵錯器命令 --- 它會執行 Python print() 函式。

pp expression

p 命令類似,除了 expression 的值是使用 pprint 模組美化後印出來的。

whatis expression

印出 expression 的型別。

source expression

嘗試獲取 expression 的原始碼並顯示它。

在 3.2 版被加入.

display [expression]

每次在當前 frame 中停止執行時,顯示 expression 的值(如果有變更)。

如果不帶有 expression,則列出當前 frame 的所有運算式。

備註

display 會對 expression 求值並將結果與之前 expression 的求值結果進行比較,因此當結果可變時,display 可能無法獲取其變更。

範例如下:

lst = []
breakpoint()
pass
lst.append(1)
print(lst)

display 不會意識到 lst 已更改,因為其求值結果在比較之前已被 lst.append(1) 原地 (in place) 修改:

> example.py(3)<module>()
-> pass
(Pdb) display lst
display lst: []
(Pdb) n
> example.py(4)<module>()
-> lst.append(1)
(Pdb) n
> example.py(5)<module>()
-> print(lst)
(Pdb)

你可以運用複製機制的一些技巧來使其能夠運作:

> example.py(3)<module>()
-> pass
(Pdb) display lst[:]
display lst[:]: []
(Pdb) n
> example.py(4)<module>()
-> lst.append(1)
(Pdb) n
> example.py(5)<module>()
-> print(lst)
display lst[:]: [1]  [old: []]
(Pdb)

在 3.2 版被加入.

undisplay [expression]

不再顯示當前 frame 中的 expression。如果不帶有 expression,則清除當前 frame 的所有顯示運算式。

在 3.2 版被加入.

interact

在從目前作用域的區域和全域命名空間初始化的新全域命名空間中啟動互動式直譯器(使用 code 模組)。可使用 exit()quit() 退出直譯器並回到偵錯器。

備註

由於 interact 為程式碼執行建立了一個新的專用命名空間,因此變數的賦值不會影響原始命名空間,但是對任何被參照的可變物件的修改將像往常一樣反映在原始命名空間中。

在 3.2 版被加入.

在 3.13 版的變更: exit()quit() 可用來退出 interact 命令。

在 3.13 版的變更: interact 將其輸出導向到偵錯器的輸出通道 (output channel),而不是 sys.stderr

alias [name [command]]

建立一個名為 name 的別名,用於執行 commandcommand 不得用引號包起來。可被替換的參數要用 %1%2、 ... 和 %9 標示,而 %* 則被所有參數替換。如果省略 command,則顯示 name 的目前別名。如果未給定引數,則列出所有別名。

巢狀別名是允許的,且可包含能在 pdb 提示字元下合法輸入的任何內容。請注意,內部 pdb 命令可以被別名所覆蓋。這樣的命令在別名被移除前都將被隱藏。別名會遞迴地應用到命令列的第一個單詞;該列內的其他單詞則不會受影響。

作為範例,這裡列出了兩個有用的別名(特別是放在 .pdbrc 檔案中時):

# 印出實例變數(用法如 "pi classInst")
alias pi for k in %1.__dict__.keys(): print(f"%1.{k} = {%1.__dict__[k]}")
# 印出 self 中的實例變數
alias ps pi self
unalias name

刪除指定的別名 name

! statement

在當前 stack frame 的情境中執行(單列)statement。除非陳述式的第一個單詞類似於偵錯器命令,否則可以省略驚嘆號,例如:

(Pdb) ! n=42
(Pdb)

要設定全域變數,你可以在同一列的賦值命令前面加上 global 陳述式,例如:

(Pdb) global list_options; list_options = ['-l']
(Pdb)
run [args ...]
restart [args ...]

重新啟動已偵錯完畢的 Python 程式。如果提供了 args,它將以 shlex 分割,並將結果用作新的 sys.argv。歷史記錄、斷點、操作和偵錯器選項均會被保留。restartrun 的別名。

q(uit)

離開偵錯器,執行中的程式會被中止。

debug code

進入一個遞迴偵錯器,逐步執行 code(這是要在當前環境中執行的任意運算式或陳述式)。

retval

印出當前函式最後一次回傳的回傳值。

exceptions [excnumber]

列出鏈接例外 (chained exceptions),或在其間跳轉。

當使用 pdb.pm()Pdb.post_mortem(...) 於鏈接例外而不是回溯時,它允許使用者在鏈接例外之間移動,使用 exceptions 命令以列出例外,並使用 exception <number> 切換到該例外。

範例如下:

def out():
    try:
        middle()
    except Exception as e:
        raise ValueError("reraise middle() error") from e

def middle():
    try:
        return inner(0)
    except Exception as e:
        raise ValueError("Middle fail")

def inner(x):
    1 / x

 out()

呼叫 pdb.pm() 將允許在例外之間移動:

> example.py(5)out()
-> raise ValueError("reraise middle() error") from e

(Pdb) exceptions
  0 ZeroDivisionError('division by zero')
  1 ValueError('Middle fail')
> 2 ValueError('reraise middle() error')

(Pdb) exceptions 0
> example.py(16)inner()
-> 1 / x

(Pdb) up
> example.py(10)middle()
-> return inner(0)

在 3.13 版被加入.

註腳