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

該模組定義了以下函式;每個函式都以稍微不同的方式進入偵錯程式

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

在偵錯程式控制下執行 *statement* (以字串或程式碼物件的形式給出)。偵錯程式提示符在執行任何程式碼之前出現;你可以設定斷點並鍵入 continue,或者你可以使用 stepnext 單步執行語句(所有這些命令在下面進行解釋)。可選的 *globals* 和 *locals* 引數指定程式碼執行的環境;預設情況下,使用模組 __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)

在呼叫堆疊幀處進入偵錯程式。這對於在程式的給定點硬編碼斷點非常有用,即使程式碼沒有進行除錯(例如,當斷言失敗時)。如果給定header,則在除錯開始之前將其列印到控制檯。

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

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

pdb.post_mortem(traceback=None)

進入給定 traceback 物件的事後除錯。如果沒有給出 traceback,它將使用當前正在處理的異常的 traceback(如果要使用預設值,則必須正在處理異常)。

pdb.pm()

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

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

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

Pdb 是偵錯程式類。

completekeystdinstdout 引數被傳遞到底層的 cmd.Cmd 類;請參閱該處的描述。

skip 引數,如果給定,必須是 glob 風格的模組名稱模式的可迭代物件。偵錯程式不會步入源自匹配其中一種模式的模組的幀。[1]

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

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

使用 skip 啟用跟蹤的示例呼叫

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

引發一個沒有引數的 審計事件 pdb.Pdb

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

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

在版本 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)。命令的引數必須用空格(空格或製表符)分隔。可選引數用方括號 ([]) 括起來,方括號不能鍵入。命令語法中的替代項用豎線 (|) 分隔。

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

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

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

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

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

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

有三個預設的便利變數

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

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

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

在版本 3.12 中新增: 添加了便利變數功能。

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

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

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

h(elp) [command]

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

w(here)

列印堆疊跟蹤,最近的幀在底部。 一個箭頭 (>) 表示當前幀,它決定了大多數命令的上下文。

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.py, relpath/file.py, modulepackage.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。 當忽略計數為零時,斷點變為活動狀態。 當非零時,每次到達斷點時,count 都會遞減,並且斷點未停用,並且任何關聯的條件都求值為 true。

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 或任何其他恢復執行的命令。

指定任何恢復執行的命令 (當前為 continue, step, next, return, jump, quit 及其縮寫) 會終止命令列表 (就好像該命令緊接著是 end)。 這是因為任何時候你恢復執行 (即使使用簡單的 next 或 step),你都可能會遇到另一個斷點 —— 它可能也有自己的命令列表,導致關於要執行哪個列表的歧義。

如果在命令列表中使用 silent 命令,則不會列印關於在斷點處停止的通常訊息。 對於要列印特定訊息然後繼續的斷點,這可能是可取的。 如果沒有其他命令列印任何內容,您將看不到斷點已到達的任何跡象。

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 表示式

在當前上下文中計算表示式並列印其值。

注意

print() 也可以使用,但它不是偵錯程式命令 — 它執行 Python print() 函式。

pp 表示式

p 命令類似,只是 表示式 的值使用 pprint 模組進行美化列印。

whatis 表示式

列印表示式的型別。

source 表示式

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

在 3.2 版本中新增。

display [表示式]

如果表示式的值發生變化,則每次執行在當前幀中停止時都顯示該值。

如果沒有 表示式,則列出當前幀的所有顯示錶達式。

注意

Display 會計算表示式,並與之前對表示式的評估結果進行比較,因此當結果是可變的,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 [表示式]

不再在當前幀中顯示表示式。如果沒有 表示式,則清除當前幀的所有顯示錶達式。

在 3.2 版本中新增。

interact

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

注意

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

在 3.2 版本中新增。

在 3.13 版本中更改: exit()quit() 可用於退出 interact 命令。

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

alias [名稱 [命令]]

建立一個名為 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

! 語句

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

(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 程式碼

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

retval

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

exceptions [excnumber]

列出或在連結的異常之間跳轉。

當使用 pdb.pm()Pdb.post_mortem(...) 以及連結的異常而不是回溯時,它允許使用者使用 exceptions 命令列出異常,並使用 exception <數字> 切換到該異常,從而在連結的異常之間移動。

示例

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 版本中新增。

腳註