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(堆疊追蹤)的標準介面。

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

importpdb;pdb.set_trace()

或:

breakpoint()

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

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

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

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

>...(2)double()->breakpoint()(Pdb)px3(Pdb)continue3*2is6

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

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

python-mpdb[-ccommand](-mmodule|pyfile)[args...]

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

-c,--command<command>

就像在.pdbrc 檔案中給定的那樣來執行命令;請參閱偵錯器命令

在 3.2 版的變更:新增了-c 選項。

-m<module>

以類似於python-m 的方式來執行模組。與腳本一樣,偵錯器將在模組的第一列之前暫停執行。

在 3.7 版的變更:新增了-m 選項。

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

>>>importpdb>>>deff(x):...print(1/x)>>>pdb.run("f(2)")> <string>(1)<module>()(Pdb) continue0.5>>>

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

>>>importpdb>>>deff(x):...print(1/x)...>>>f(0)Traceback (most recent call last):  File"<stdin>", line1, in<module>  File"<stdin>", line2, infZeroDivisionError:division by zero>>>pdb.pm()> <stdin>(2)f()(Pdb) p x0(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(t=None)

進入所給定例外或回溯物件的事後偵錯。如果沒有給定,預設使用目前正在處理的例外,或如果沒有例外則會引發ValueError

在 3.13 版的變更:新增對例外物件的支援。

pdb.pm()

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

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

classpdb.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 引數的呼叫示範:

importpdb;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 時,印出有關該命令的幫助訊息,helppdb 會顯示完整文件(即pdb 模組的說明字串 (docstring))。由於command 引數必須是一個識別字 (identifier),若要取得! 命令的幫助訊息則必須輸入helpexec

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 引數,則清除此列上的所有斷點。如果引數是空格分隔的斷點編號列表,則清除這些斷點。如果不帶引數則清除所有斷點(但會先提示確認)。

disablebpnumber[bpnumber...]

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

enablebpnumber[bpnumber...]

啟用指定的斷點。

ignorebpnumber[count]

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

conditionbpnumber[condition]

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

commands[bpnumber]

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

(Pdb)commands1(com)psome_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)

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

pexpression

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

備註

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

ppexpression

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

whatisexpression

印出expression 的型別。

sourceexpression

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

在 3.2 版被加入.

display[expression]

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

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

備註

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

範例如下:

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

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

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

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

>example.py(3)<module>()->pass(Pdb)displaylst[:]displaylst[:]:[](Pdb)n>example.py(4)<module>()->lst.append(1)(Pdb)n>example.py(5)<module>()->print(lst)displaylst[:]:[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")aliaspiforkin%1.__dict__.keys():print(f"%1.{k} ={%1.__dict__[k]}")# 印出 self 中的實例變數aliaspspiself
unaliasname

刪除指定的別名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)

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

debugcode

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

retval

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

exceptions[excnumber]

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

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

範例如下:

defout():try:middle()exceptExceptionase:raiseValueError("reraise middle() error")fromedefmiddle():try:returninner(0)exceptExceptionase:raiseValueError("Middle fail")definner(x):1/xout()

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

>example.py(5)out()->raiseValueError("reraise middle() error")frome(Pdb)exceptions0ZeroDivisionError('division by zero')1ValueError('Middle fail')>2ValueError('reraise middle() error')(Pdb)exceptions0>example.py(16)inner()->1/x(Pdb)up>example.py(10)middle()->returninner(0)

在 3.13 版被加入.

註腳

[1]

一個 frame 是否會被認為源自特定模組是由該 frame 全域性變數中的__name__ 來決定的。