pdb — Python 偵錯程式

原始碼: Lib/pdb.py


模組 pdb 為 Python 程式定義了一個互動式原始碼偵錯程式。它支援在原始碼行級別設定(條件)斷點和單步執行、檢查堆疊幀、列出原始碼以及在任何堆疊幀的上下文中評估任意 Python 程式碼。它還支援事後除錯,並可以由程式控制呼叫。

該偵錯程式是可擴充套件的——它實際上被定義為 Pdb 類。這一點目前沒有文件說明,但透過閱讀原始碼很容易理解。其擴充套件介面使用了 bdbcmd 模組。

參見

模組 faulthandler

用於在發生故障、超時或收到使用者訊號時,顯式地轉儲 Python 回溯資訊。

模組 traceback

用於提取、格式化和列印 Python 程式堆疊回溯資訊的標準介面。

進入偵錯程式的典型用法是在你想進入偵錯程式的位置插入

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 [-c command] (-m module | -p pid | pyfile) [args ...]

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

-c, --command <command>

執行命令,如同這些命令是在 .pdbrc 檔案中給出的一樣;參見 偵錯程式命令

在 3.2 版更改: 添加了 -c 選項。

-m <module>

以類似於 python -m 的方式執行模組。與指令碼一樣,偵錯程式將在模組的第一行之前暫停執行。

在 3.7 版更改: 添加了 -m 選項。

-p, --pid <pid>

附加到指定 PID 的程序。

在 3.14 版本加入。

要附加到一個正在執行的 Python 程序進行遠端除錯,請使用 -p--pid 選項並提供目標程序的 PID:

python -m pdb -p 1234

備註

附加到一個在系統呼叫中阻塞或等待 I/O 的程序,只有在下一個位元組碼指令被執行或程序收到訊號時才會生效。

在偵錯程式控制下執行一條語句的典型用法是:

>>> 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 進行的名稱賦值將立即影響活動作用域,即使在 最佳化作用域 內執行也是如此。

該模組定義了以下函式;每個函式以略有不同的方式進入偵錯程式:

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, commands=None)

在呼叫堆疊幀處進入偵錯程式。這對於在程式中的給定點硬編碼一個斷點非常有用,即使程式碼沒有以其他方式進行除錯(例如,當斷言失敗時)。如果給出 header,它會在除錯開始前列印到控制檯。如果給出 commands 引數,它是一個在偵錯程式啟動時要執行的命令列表。

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

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

3.14 版新加入: commands 引數。

awaitable pdb.set_trace_async(*, header=None, commands=None)

set_trace() 的非同步版本。此函式應在非同步函式內與 await 一起使用。

async def f():
    await pdb.set_trace_async()

如果偵錯程式由此函式呼叫,則支援 await 語句。

在 3.14 版本加入。

pdb.post_mortem(t=None)

進入給定異常或 回溯物件 的事後除錯。如果未提供值,則使用當前正在處理的異常,如果沒有異常,則引發 ValueError

在 3.13 版更改: 添加了對異常物件的支援。

pdb.pm()

進入在 sys.last_exc 中找到的異常的事後除錯。

pdb.set_default_backend(backend)

pdb 有兩個受支援的後端:'settrace''monitoring'。詳情請參見 bdb.Bdb。使用者可以在例項化 Pdb 時設定要使用的預設後端,如果未指定。如果未指定後端,預設值為 'settrace'

備註

breakpoint()set_trace() 不會受此函式影響。它們總是使用 'monitoring' 後端。

在 3.14 版本加入。

pdb.get_default_backend()

返回 pdb 的預設後端。

在 3.14 版本加入。

run* 函式和 set_trace() 是例項化 Pdb 類並呼叫同名方法的別名。如果你想訪問更多功能,你必須自己動手:

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

Pdb 是偵錯程式類。

completekeystdinstdout 引數被傳遞給底層的 cmd.Cmd 類;請參閱那裡的描述。

skip 引數(如果給出)必須是 glob 風格模組名模式的可迭代物件。偵錯程式不會進入源於匹配這些模式之一的模組的幀。[1]

預設情況下,當你給出 continue 命令時,Pdb 會為 SIGINT 訊號(當用戶在控制檯上按 Ctrl-C 時傳送)設定一個處理程式。這允許你透過按 Ctrl-C 再次進入偵錯程式。如果你希望 Pdb 不觸碰 SIGINT 處理程式,請將 nosigint 設定為 true。

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

mode 引數指定偵錯程式是如何被呼叫的。它會影響一些偵錯程式命令的工作方式。有效值為 'inline'(由 breakpoint() 內建函式使用)、'cli'(由命令列呼叫使用)或 None(用於向後相容的行為,如同新增 mode 引數之前一樣)。

backend 引數指定偵錯程式使用的後端。如果傳入 None,將使用預設後端。參見 set_default_backend()。否則,支援的後端是 'settrace''monitoring'

colorize 引數,如果設定為 True,將在偵錯程式中啟用彩色輸出(如果支援顏色的話)。這將高亮顯示 pdb 中顯示的原始碼。

啟用帶有 skip 的跟蹤的示例呼叫:

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

引發一個不帶引數的 審計事件 pdb.Pdb

在 3.1 版更改: 添加了 skip 引數。

在 3.2 版更改: 添加了 nosigint 引數。以前,Pdb 從未設定過 SIGINT 處理程式。

在 3.6 版更改: readrc 引數。

3.14 版新加入: 添加了 mode 引數。

3.14 版新加入: 添加了 backend 引數。

3.14 版新加入: 添加了 colorize 引數。

在 3.14 版更改: breakpoint()pdb.set_trace() 這樣的內聯斷點將始終在呼叫幀處停止程式,忽略 skip 模式(如果有的話)。

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

請參閱上面解釋的函式的文件。

偵錯程式命令

偵錯程式識別的命令如下所示。大多數命令可以縮寫為一個或兩個字母,如所示;例如,h(elp) 意味著可以使用 hhelp 來輸入幫助命令(但不能是 hehel,也不能是 HHelpHELP)。命令的引數必須用空白(空格或製表符)分隔。可選引數在命令語法中用方括號([])括起來;方括號本身不應輸入。命令語法中的備選項用豎線(|)分隔。

輸入一個空行會重複上一個輸入的命令。例外:如果上一個命令是 list 命令,則會列出接下來的 11 行。

偵錯程式不認識的命令被假定為 Python 語句,並在被除錯程式的上下文中執行。Python 語句也可以用感嘆號(!)作為字首。這是檢查被除錯程式的一種強大方式;甚至可以更改變數或呼叫函式。當此類語句中發生異常時,會列印異常名稱,但偵錯程式的狀態不會改變。

在 3.13 版更改: 字首是 pdb 命令的表示式/語句現在可以被正確識別和執行。

偵錯程式支援別名。別名可以有引數,這使得它在一定程度上能夠適應被檢查的上下文。

可以在單行中輸入多個命令,用 ;; 分隔。(單個 ; 不被使用,因為它是傳遞給 Python 解析器的一行中多個命令的分隔符。)在分隔命令時沒有應用任何智慧;輸入在第一個 ;; 對處被分割,即使它位於帶引號的字串中間。對於包含雙分號的字串,一個變通方法是使用隱式字串拼接 ';'';'";"";"

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

有四個預設的*便捷變數*:

  • $_frame:你正在除錯的當前幀

  • $_retval:如果幀正在返回,則為返回值

  • $_exception:如果幀正在引發異常,則為該異常

  • $_asynctask:如果 pdb 在非同步函式中停止,則為 asyncio 任務

3.12 版新加入: 添加了*便捷變數*功能。

3.14 版新加入: 添加了 $_asynctask 便捷變數。

如果使用者的家目錄或當前目錄中存在檔案 .pdbrc,它將以 'utf-8' 編碼讀取並執行,就像在偵錯程式提示符下輸入一樣,但空行和以 # 開頭的行將被忽略。這對別名特別有用。如果兩個檔案都存在,則首先讀取家目錄中的檔案,並且其中定義的別名可以被本地檔案覆蓋。

在 3.2 版更改: .pdbrc 現在可以包含繼續除錯的命令,例如 continuenext。以前,這些命令沒有效果。

在 3.11 版更改: .pdbrc 現在以 'utf-8' 編碼讀取。以前,它以系統區域設定編碼讀取。

h(elp) [command]

不帶引數時,列印可用命令的列表。帶一個 command 作為引數時,列印關於該命令的幫助資訊。help pdb 顯示完整的文件(pdb 模組的文件字串)。由於 command 引數必須是識別符號,因此必須輸入 help exec 來獲取關於 ! 命令的幫助。

w(here) [count]

列印堆疊跟蹤,最新的幀在底部。如果 count 為 0,列印當前幀條目。如果 count 為負數,列印最舊的 -count 個幀。如果 count 為正數,列印最新的 count 個幀。一個箭頭(>)指示當前幀,它決定了大多數命令的上下文。

在 3.14 版更改: 添加了 count 引數。

d(own) [count]

在堆疊跟蹤中將當前幀向下移動 count(預設為 1)層(到一個較新的幀)。

u(p) [count]

在堆疊跟蹤中將當前幀向上移動 count(預設為 1)層(到一個較舊的幀)。

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。當忽略計數為零時,斷點變為活動狀態。當非零時,每次到達斷點並且斷點未被停用且任何相關條件評估為 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 指的是最後設定的斷點。

你可以使用斷點命令來重新啟動你的程式。只需使用 continue 命令、step 或任何其他恢復執行的命令。

指定任何恢復執行的命令(目前是 continuestepnextreturnuntiljumpquit 及其縮寫)會終止命令列表(就好像該命令後面立即跟了 end)。這是因為任何時候你恢復執行(即使是簡單的 next 或 step),你都可能遇到另一個斷點——它可能有自己的命令列表,導致關於執行哪個列表的歧義。

如果命令列表中包含 silent 命令,或者一個恢復執行的命令,那麼包含幀資訊的斷點訊息將不會顯示。

在 3.14 版更改: 如果命令列表中存在恢復執行的命令,則不會顯示幀資訊。

s(tep)

執行當前行,在第一個可能的機會停下來(無論是在被呼叫的函式中,還是在當前函式的下一行)。

n(ext)

繼續執行,直到到達當前函式的下一行或函式返回。(nextstep 的區別在於,step 會在被呼叫的函式內部停止,而 next 會以(幾乎)全速執行被呼叫的函式,只在當前函式的下一行停止。)

unt(il) [lineno]

不帶引數時,繼續執行直到到達行號大於當前行的那一行。

lineno 時,繼續執行直到到達行號大於或等於 lineno 的那一行。在這兩種情況下,噹噹前幀返回時也會停止。

在 3.2 版更改: 允許給出一個明確的行號。

r(eturn)

繼續執行,直到當前函式返回。

c(ont(inue))

繼續執行,只在遇到斷點時停止。

j(ump) lineno

設定下一行將要執行的程式碼。只在最底層的幀中可用。這讓你能跳回並再次執行程式碼,或者向前跳過你不想執行的程式碼。

需要注意的是,並非所有的跳轉都是允許的——例如,不可能跳入 for 迴圈的中間或跳出 finally 子句。

l(ist) [first[, last]]

列出當前檔案的原始碼。不帶引數時,列出當前行周圍的 11 行或繼續上一次的列表。以 . 作為引數時,列出當前行周圍的 11 行。帶一個引數時,列出該行周圍的 11 行。帶兩個引數時,列出給定的範圍;如果第二個引數小於第一個,它被解釋為一個計數。

當前幀中的當前行由 -> 指示。如果正在除錯一個異常,異常最初被引發或傳播的行由 >> 指示,如果它與當前行不同。

在 3.2 版更改: 添加了 >> 標記。

ll | longlist

列出當前函式或幀的所有原始碼。感興趣的行會像 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]

如果 expression 的值發生變化,每次在當前幀中停止執行時顯示其值。

不帶 expression 時,列出當前幀的所有 display 表示式。

備註

Display 會評估 expression 並將其與上次評估 expression 的結果進行比較,因此當結果是可變的時,display 可能無法捕捉到變化。

示例

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

Display 不會意識到 lst 已經被改變,因為評估結果在被比較之前被 lst.append(1) 就地修改了。

> 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]

不再在當前幀中顯示 expression。不帶 expression 時,清除當前幀的所有 display 表示式。

在 3.2 版本加入。

interact

在一個新的全域性名稱空間中啟動一個互動式直譯器(使用 code 模組),該名稱空間從當前作用域的區域性和全域性名稱空間初始化。使用 exit()quit() 退出直譯器並返回到偵錯程式。

備註

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

在 3.2 版本加入。

在 3.13 版更改: 可以使用 exit()quit() 退出 interact 命令。

在 3.13 版更改: interact 將其輸出定向到偵錯程式的輸出通道,而不是 sys.stderr

alias [name [command]]

建立一個名為 name 的別名,用於執行 commandcommand 必須*不*用引號括起來。可替換的引數可以用 %1%2、... 和 %9 來表示,而 %* 被所有引數替換。如果省略 command,則顯示 name 的當前別名。如果不給出任何引數,則列出所有別名。

別名可以巢狀,並且可以包含任何可以合法地在 pdb 提示符下輸入的內容。請注意,內部的 pdb 命令*可以*被別名覆蓋。這樣的命令就會被隱藏,直到別名被移除。別名遞迴地應用於命令列的第一個詞;行中的所有其他詞都保持不變。

作為一個例子,這裡有兩個有用的別名(尤其是在放在 .pdbrc 檔案中時):

# Print instance variables (usage "pi classInst")
alias pi for k in %1.__dict__.keys(): print(f"%1.{k} = {%1.__dict__[k]}")
# Print instance variables in self
alias ps pi self
unalias name

刪除指定的別名 name

! statement

在當前堆疊幀的上下文中執行(單行)statement。除非語句的第一個詞與偵錯程式命令相似,否則可以省略感嘆號,例如:

(Pdb) ! n=42
(Pdb)

要設定一個全域性變數,你可以在同一行上用 global 語句作為賦值命令的字首,例如:

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

重新啟動被除錯的 Python 程式。如果提供了 args,它會用 shlex 進行分割,結果用作新的 sys.argv。歷史、斷點、動作和偵錯程式選項都會被保留。restartrun 的一個別名。

在 3.14 版更改: 當偵錯程式以 'inline' 模式呼叫時,runrestart 命令被停用。

q(uit)

退出偵錯程式。正在執行的程式將被中止。檔案結束輸入等同於 quit

如果偵錯程式以 'inline' 模式呼叫,將顯示一個確認提示。yY<Enter>EOF 都將確認退出。

在 3.14 版更改: 如果偵錯程式以 'inline' 模式呼叫,將顯示一個確認提示。確認後,偵錯程式將立即呼叫 sys.exit(),而不是在下一個跟蹤事件中引發 bdb.BdbQuit

debug code

進入一個遞迴偵錯程式,單步執行 code(它是在當前環境中執行的任意表達式或語句)。

retval

列印當前函式最後一次返回的返回值。

exceptions [excnumber]

列出或在鏈式異常之間跳轉。

當使用 pdb.pm()Pdb.post_mortem(...) 處理鏈式異常而不是回溯時,它允許使用者使用 exceptions 命令列出異常,並使用 exceptions <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 版本加入。

腳註