主頁(yè) > 知識(shí)庫(kù) > 如何使用pdb進(jìn)行Python調(diào)試

如何使用pdb進(jìn)行Python調(diào)試

熱門標(biāo)簽:銀行業(yè)務(wù) 美圖手機(jī) 呼叫中心市場(chǎng)需求 檢查注冊(cè)表項(xiàng) 網(wǎng)站文章發(fā)布 智能手機(jī) 鐵路電話系統(tǒng) 服務(wù)器配置

調(diào)試應(yīng)用有時(shí)是一個(gè)不受歡迎的工作,當(dāng)你長(zhǎng)期編碼之后,只希望寫的代碼順利運(yùn)行。但是,很多情況下,我們需要學(xué)習(xí)一個(gè)新的語(yǔ)言功能或者實(shí)驗(yàn)檢測(cè)新的方法,從而去理解其中運(yùn)行的機(jī)制原理。

即使不考慮這樣的場(chǎng)景,調(diào)試代碼仍然是有必要的,所以學(xué)會(huì)在工作中使用調(diào)試器是很重要的。本篇教程中,我將會(huì)給出基本的使用關(guān)于pdb----Python‘s interative source code debugger。

首先給出一些pdb的基礎(chǔ)知識(shí),大家可以保存這篇文章方便后續(xù)的閱讀。pdb類似于其他的調(diào)試器,是獨(dú)立的工具,當(dāng)你需要一個(gè)調(diào)試器時(shí),它們是不可替代的。本篇教程的最后,大家將會(huì)學(xué)習(xí)到如何使用調(diào)試器來查看應(yīng)用中的任何變量,可以在任何時(shí)刻停止或恢復(fù)應(yīng)用執(zhí)行流程,從而了解到每行代碼是如何影響應(yīng)用的內(nèi)部狀態(tài)的。

這有助于追蹤難以發(fā)現(xiàn)的bug,并且實(shí)現(xiàn)快速可靠的解決缺陷代碼。有時(shí)候,在pdb中單步調(diào)試代碼,然后查看變量值的變化,可以有助于我們對(duì)應(yīng)用代碼的深層次理解。pdb是Python標(biāo)準(zhǔn)庫(kù)的一部分,所以只要我們使用了Python解釋器,也就可以使用pdb,非常方便。

本文所使用的實(shí)例代碼會(huì)放在文章的末尾,使用的環(huán)境是Python3.6及以后的解釋器,大家可以下載源碼方便學(xué)習(xí)。

1.開始階段:答應(yīng)一個(gè)變量值

開始案例,我們首先探索pdb最簡(jiǎn)單的使用:查閱一個(gè)變量的值。首先,我們?cè)谝粋€(gè)源碼文件中某一行,寫入下列語(yǔ)句:

import pdb; pdb.set_trace()

當(dāng)這一行代碼被執(zhí)行后,Python代碼文件就會(huì)被暫停運(yùn)行,等待你發(fā)布命令來指導(dǎo)它下一步如何操作。運(yùn)行上面的代碼后,你可以在命令行界面看到(Pdb)的提示符,這就意味著,代碼被停止了,等待命令的輸入。

自從Python3.7以來,官方標(biāo)準(zhǔn)建議使用標(biāo)準(zhǔn)庫(kù)內(nèi)部函數(shù)breakpoint()替代上面的代碼(注意:本文附帶的代碼都使用的上面代碼形式),這樣可以更加加快解釋器的運(yùn)行和調(diào)試:

breakpoint()

默認(rèn)情況下,breakpoint()將會(huì)倒入pdb模塊,然后調(diào)用pdb.set_trace()函數(shù),只是它進(jìn)行了進(jìn)一步封裝。但是,使用breakpoint()可以更加靈活,并且允許用戶通過調(diào)用它的API來控制調(diào)試行為,以及使用環(huán)境變量PYTHONBREAKPOINT。例如,當(dāng)我們?cè)O(shè)置PYTHONBREAKPOINT=0在我們的環(huán)境中,這就會(huì)完全關(guān)閉breakpoint()的功能,從而關(guān)閉調(diào)試功能。

此外,我們還可以不用手動(dòng)在源代碼文件中加入斷點(diǎn)代碼,只需要在命令行輸入運(yùn)行指令時(shí)通過參數(shù)傳遞來設(shè)置,例如:

$ python3 -m pdb app.py arg1 arg2

那么,讓我們直接進(jìn)入本小節(jié)的內(nèi)容,也就是查閱代碼中變量的值,看下面的案例,使用的源碼文件是codeExample1.py:

#!/usr/bin/env python3

filename = __file__
import pdb; pdb.set_trace()
print(f'path={filename}')

而在你命令行界面,運(yùn)行上述的Python代碼,你就可以得到下面的輸出結(jié)果:

$ ./codeExample1.py
> /code/codeExample1.py(5)module>()
-> print(f'path={filename}')
(Pdb)

接下來,讓我們輸入p filename這個(gè)命令,來查看filename這個(gè)變量的值,可以看到下述結(jié)果:

(Pdb)	p filename
'./codeExample1.py'
(Pdb)

因?yàn)槲覀兪褂玫氖且粋€(gè)命令行接口界面程序(command-line interface),那么注意一下輸出的字符和格式,解釋如下:

  • >開始的第一行告訴我們所運(yùn)行的源碼文件名稱,源碼名稱之后,用小括號(hào)包含的是當(dāng)前代碼行數(shù),再后面就是函數(shù)名稱。這里,因?yàn)槲覀儧]用調(diào)用任何函數(shù),而是處于模塊級(jí)別,所用見到的是()。
  • ->開始的第二行表示目前行數(shù)代碼對(duì)應(yīng)的具體代碼內(nèi)容。
  • (Pdb)是一個(gè)pdb提示符,等待下一個(gè)命令的輸入。

我們可以使用q命令,表示推出調(diào)試(quit)。

2.打印表達(dá)式

當(dāng)使用命令p,我們同樣可以輸入一個(gè)表達(dá)式,讓Python來計(jì)算表達(dá)式的值。 如果傳入一個(gè)變量名,pdb就會(huì)答應(yīng)當(dāng)前變量對(duì)應(yīng)的值。但是,我們可以進(jìn)一步調(diào)查我們應(yīng)用程序當(dāng)前的運(yùn)行狀態(tài)。

下面案例中,當(dāng)函數(shù)get_path()被調(diào)用,為了查看在這個(gè)函數(shù)中發(fā)生了什么,我已經(jīng)提前插入斷點(diǎn)程序pdb.set_trace(),來阻斷程序的運(yùn)行,源碼codeExample2.py內(nèi)容如下:

#!/usr/bin/env python3
import os
def get_path(filename):
    """Return file's path or empty string if no path."""
    head, tail = os.path.split(filename)
    import pdb; pdb.set_trace()
    return head

filename = __file__
print(f'path = {get_path(filename)}')

如果在命令行中運(yùn)行這個(gè)文件,可以得到下面的輸出:

$ ./codeExample2.py 
> /code/example2.py(10)get_path()
-> return head
(Pdb) 

那么此時(shí),我們處于什么階段:

  • >:表示我們?cè)谠创a文件codeExample2.py的第10行代碼處,此處是函數(shù)get_path()。如果運(yùn)行命令p輸出的標(biāo)量,就是當(dāng)前所引用的代碼幀,也就是當(dāng)前上下文內(nèi)的標(biāo)量。
  • ->:運(yùn)行的代碼已經(jīng)被停止在return head處,這一行代碼還沒有被執(zhí)行,是位于codeExample2.py文件的第10行處,具體是在函數(shù)get_path()內(nèi)。

如果想要查看應(yīng)用當(dāng)前狀態(tài)代碼上下文情況,可以使用命令ll(longlist),來查看代碼內(nèi)容,具體內(nèi)容如下:

(Pdb) ll
  6     def get_path(filename):
  7         """Return file's path or empty string if no path."""
  8         head, tail = os.path.split(filename)
  9         import pdb; pdb.set_trace()
 10  ->     return head
(Pdb) p filename
'./codeExample2.py'
(Pdb) p head, tail
('.', 'codeExample2.py')
(Pdb) p 'filename: ' + filename
'filename: ./codeExample2.py'
(Pdb) p get_path
function get_path at 0x100760e18>
(Pdb) p getattr(get_path, '__doc__')
"Return file's path or empty string if no path."
(Pdb) p [os.path.split(p)[1] for p in os.path.sys.path]
['pdb-basics', 'python36.zip', 'python3.6', 'lib-dynload', 'site-packages']
(Pdb) 

你可以輸入任何有效的Python表達(dá)式接在p后面,來進(jìn)行計(jì)算。當(dāng)你正在調(diào)試并希望在運(yùn)行時(shí)直接在應(yīng)用程序中測(cè)試替代實(shí)現(xiàn)時(shí),這尤其有用。您還可以使用命令 pp(漂亮打?。﹣砻烙^打印表達(dá)式。 如果您想打印具有大量輸出的變量或表達(dá)式,例如列表和字典。 如果可以,漂亮打印將對(duì)象保留在一行上,如果它們不適合允許的寬度,則將它們分成多行。

3.調(diào)試代碼

在這一部分,我們主要是用兩個(gè)命令來實(shí)現(xiàn)代碼的調(diào)試,如下圖所示:

命令n(next)和s(step)的區(qū)別是,pdb運(yùn)行停止的位置。使用n(next),pdb會(huì)進(jìn)行執(zhí)行,直到運(yùn)行到當(dāng)前函數(shù)或模塊的下一行代碼,即:如果有外部函數(shù)被調(diào)用,不會(huì)跳轉(zhuǎn)到外部函數(shù)代碼中,可以把n理解為“step over”。使用s(step)來執(zhí)行當(dāng)前代碼,但是如果有外部函數(shù)被調(diào)用,會(huì)跳轉(zhuǎn)到外部函數(shù)中,可以理解為“step into”,如果執(zhí)行到外部跳轉(zhuǎn)函數(shù),s命令會(huì)輸出--Call--。

n(next)和s(step)命令都會(huì)暫定代碼的執(zhí)行,當(dāng)運(yùn)行到當(dāng)前函數(shù)的結(jié)束部分,并且打印出--Return--,下面是codeExample3.py文件源碼內(nèi)容:

#!/usr/bin/env python3

import os

def get_path(filename):
    """Return file's path or empty string if no path."""
    head, tail = os.path.split(filename)
    return head

filename = __file__
import pdb; pdb.set_trace()
filename_path = get_path(filename)
print(f'path = {filename_path}')

如果在命令行中運(yùn)行這個(gè)文件,同時(shí)輸入命令n,可以得到下面的輸出:

$ ./codeExample3.py 
> /code/example3.py(14)module>()
-> filename_path = get_path(filename)
(Pdb) n
> /code/example3.py(15)module>()
-> print(f'path = {filename_path}')
(Pdb) 

通過使用命令n(next),我們停止在15行代碼,并且我們也只是在此模塊中,沒有跳轉(zhuǎn)到函數(shù)get_path()。此處函數(shù)表示為(),表明我們目前是處于模塊級(jí)別而不是任何函數(shù)內(nèi)部。

再讓我們嘗試使用s(step)命令,輸出為:

$ ./codeExample3.py 
> /code/example3.py(14)module>()
-> filename_path = get_path(filename)
(Pdb) s
--Call--
> /code/example3.py(6)get_path()
-> def get_path(filename):
(Pdb) 

通過使用s(step)命令,我們停止在函數(shù)get_path()內(nèi)部的第6行代碼處,因?yàn)檫@個(gè)函數(shù)是在代碼文件中第14行處被調(diào)用的,注意在s命令之后輸出了--Call--,表明是函數(shù)調(diào)用。為了方便,pdb有命令記憶功能,如果我們要調(diào)試很多代碼,可以輸入Enter回車鍵,來重復(fù)執(zhí)行命令。

下面是我們混合使用n(next)和s(step)命令的案例,首先輸入s(step),因?yàn)槲覀兿胍M(jìn)入函數(shù)get_path(),然后通過命令n(next)在局部調(diào)試代碼,并且使用Enter回車鍵,避免重復(fù)輸入命令:

$ ./codeExample3.py 
> /code/codeExample3.py(14)module>()
-> filename_path = get_path(filename)
(Pdb) s
--Call--
> /code/codeExample3.py(6)get_path()
-> def get_path(filename):
(Pdb) n
> /code/codeExample3.py(8)get_path()
-> head, tail = os.path.split(filename)
(Pdb) 
> /code/codeExample3.py(9)get_path()
-> return head
(Pdb) 
--Return--
> /code/codeExample3.py(9)get_path()->'.'
-> return head
(Pdb) 
> /code/codeExample3.py(15)module>()
-> print(f'path = {filename_path}')
(Pdb) 
path = .
--Return--
> /code/codeExample3.py(15)module>()->None
-> print(f'path = {filename_path}')
(Pdb) 

注意輸出的--Call--和--Return--,這是pdb輸出的信息,提示我們調(diào)試過程的狀態(tài)信息,n(next)和s(step)命令都會(huì)在函數(shù)返回后停止,這也就是我們會(huì)看到--Return--信息的輸出。此外,還要注意->'.'在第一個(gè)--Return--輸出上面的結(jié)尾處:

--Return--
> /code/example3.py(9)get_path()->'.'
-> return head
(Pdb) 

當(dāng)pdb停止到一個(gè)函數(shù)的結(jié)尾,但是還沒有運(yùn)行到return時(shí),pdb同樣會(huì)打印return值,在上面例子中就是'.'.

3.1顯示代碼

不要忘記,我們上面提到了命令ll(longlist:顯示當(dāng)前函數(shù)或幀的源代碼),在我們調(diào)試進(jìn)入到不熟悉的代碼上下文中,這個(gè)命令非常有效,我們可以打印出整個(gè)函數(shù)代碼,顯示樣例如下:

$ ./codeExample3.py 
> /code/codeExample3.py(14)module>()
-> filename_path = get_path(filename)
(Pdb) s
--Call--
> /code/codeExample3.py(6)get_path()
-> def get_path(filename):
(Pdb) ll
  6  -> def get_path(filename):
  7         """Return file's path or empty string if no path."""
  8         head, tail = os.path.split(filename)
  9         return head
(Pdb) 

如果想要查看簡(jiǎn)短的代碼片段,我們可以使用命令l(list),不需要輸入?yún)?shù),它將會(huì)打印11行目前代碼附近的代碼內(nèi)容,案例如下:

$ ./codeExample3.py 
> /code/codeExample3.py(14)module>()
-> filename_path = get_path(filename)
(Pdb) l
  9         return head
 10     
 11     
 12     filename = __file__
 13     import pdb; pdb.set_trace()
 14  -> filename_path = get_path(filename)
 15     print(f'path = {filename_path}')
[EOF]
(Pdb) l
[EOF]
(Pdb) l .
  9         return head
 10     
 11     
 12     filename = __file__
 13     import pdb; pdb.set_trace()
 14  -> filename_path = get_path(filename)
 15     print(f'path = {filename_path}')
[EOF]
(Pdb) 

4.斷點(diǎn)使用

斷點(diǎn)的正確使用,在我們調(diào)試過程中可以節(jié)約大量的時(shí)間。不需要單步調(diào)試一行行代碼,而只用簡(jiǎn)單地在我們想要查閱的地方設(shè)置一個(gè)斷點(diǎn),就可以直接運(yùn)行到斷點(diǎn)處進(jìn)行調(diào)試。同樣的,我們可以添加條件,來讓pdb判斷是否需要設(shè)置斷點(diǎn)。通常使用命令b(break)來設(shè)置一個(gè)斷點(diǎn),我們可以通過指定代碼行數(shù),或者是想要調(diào)試的函數(shù)名稱,語(yǔ)法如下:

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

如果filename:沒有在代碼行數(shù)之前被指定,那么默認(rèn)就是當(dāng)前代碼文件中。注意第二個(gè)可選參數(shù)是b: condition,這個(gè)功能非常強(qiáng)大。假設(shè)在一個(gè)場(chǎng)景下,我們想要在某種條件成立的情況下設(shè)置斷點(diǎn),如果我們傳入一個(gè)表達(dá)式座位第二個(gè)參數(shù),pdb會(huì)在計(jì)算改表達(dá)式為true的情況下設(shè)置斷點(diǎn),我們會(huì)在下面給出案例。下面案例中,使用了一個(gè)工具模塊util.py,讓我們?cè)诤瘮?shù)get_path()中設(shè)置一個(gè)斷點(diǎn),下面是代碼codeExample4.py的內(nèi)容:

#!/usr/bin/env python3

import util

filename = __file__
import pdb; pdb.set_trace()
filename_path = util.get_path(filename)
print(f'path = {filename_path}')

下面是工具模塊util.py文件內(nèi)容:

def get_path(filename):
    """Return file's path or empty string if no path."""
    import os
    head, tail = os.path.split(filename)
    return head

首先,讓我們使用源碼文件名稱和代碼行數(shù)來設(shè)置斷點(diǎn):

$ ./example4.py 
> /code/example4.py(7)module>()
-> filename_path = util.get_path(filename)
(Pdb) b util:5
Breakpoint 1 at /code/util.py:5
(Pdb) c
> /code/util.py(5)get_path()
-> return head
(Pdb) p filename, head, tail
('./example4.py', '.', 'example4.py')
(Pdb) 

命令c(continue)是實(shí)現(xiàn)被斷點(diǎn)停止后繼續(xù)運(yùn)行的命令,下面,讓我們使用函數(shù)名來設(shè)置斷點(diǎn):

$ ./codeExample4.py 
> /code/codeExample4.py(7)module>()
-> filename_path = util.get_path(filename)
(Pdb) b util.get_path
Breakpoint 1 at /code/util.py:1
(Pdb) c
> /code/util.py(3)get_path()
-> import os
(Pdb) p filename
'./codeExample4.py'
(Pdb) 

如果輸入b,并且不帶任何參數(shù),就可以查看到所有已經(jīng)設(shè)置的斷點(diǎn)信息:

(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /code/util.py:1
(Pdb) 

你可以使用命令 disable bpnumber 和 enable bpnumber 禁用和重新啟用斷點(diǎn)。 bpnumber 是斷點(diǎn)列表第一列 Num 中的斷點(diǎn)編號(hào)。 注意 Enb 列的值變化:

(Pdb) disable 1
Disabled breakpoint 1 at /code/util.py:1
(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep no    at /code/util.py:1
(Pdb) enable 1
Enabled breakpoint 1 at /code/util.py:1
(Pdb) b
Num Type         Disp Enb   Where
1   breakpoint   keep yes   at /code/util.py:1
(Pdb) 

為了刪除一個(gè)斷點(diǎn),可以使用命令cl(clear):

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

現(xiàn)在,讓我們嘗試在設(shè)置斷點(diǎn)時(shí)輸入表達(dá)式參數(shù),在當(dāng)前案例場(chǎng)景下,get_path()函數(shù)如果接受到一個(gè)相對(duì)路徑,即:如果路徑名稱不是以/開始的,那么就不會(huì)設(shè)置斷點(diǎn),具體案例如下:

$ ./codeExample4.py 
> /code/codeExample4.py(7)module>()
-> filename_path = util.get_path(filename)
(Pdb) b util.get_path, not filename.startswith('/')
Breakpoint 1 at /code/util.py:1
(Pdb) c
> /code/util.py(3)get_path()
-> import os
(Pdb) a
filename = './codeExample4.py'
(Pdb) 

如果創(chuàng)建一個(gè)斷點(diǎn)后,繼續(xù)輸入c(continue)命令來運(yùn)行,pdb只會(huì)在表達(dá)式計(jì)算為true的情況下停止運(yùn)行。命令a(args)會(huì)打印出當(dāng)前函數(shù)的傳入?yún)?shù)。

上述案例中,如果你通過函數(shù)名稱而不是代碼行數(shù)來設(shè)置斷點(diǎn),注意表達(dá)式只會(huì)使用當(dāng)前函數(shù)的參數(shù)或者全局變量,不然的話,斷點(diǎn)就會(huì)不去計(jì)算表達(dá)式,而直接停止函數(shù)運(yùn)行。如果,我們還是想用不是當(dāng)前的函數(shù)變量來計(jì)算表達(dá)式,也就是使用的變量不在當(dāng)前函數(shù)參數(shù)列表中,那么就要指定代碼行數(shù),案例如下:

$ ./codeExample4.py 
> /code/codeExample4.py(7)module>()
-> filename_path = util.get_path(filename)
(Pdb) b util:5, not head.startswith('/')
Breakpoint 1 at /code/util.py:5
(Pdb) c
> /code/util.py(5)get_path()
-> return head
(Pdb) p head
'.'
(Pdb) a
filename = './codeExample4.py'
(Pdb) 

5.繼續(xù)執(zhí)行代碼

目前,我們可以使用n(next)和s(step)命令來調(diào)試查看代碼,然后使用命令b(break)和c(continue)來停止或繼續(xù)代碼的運(yùn)行,這里還有一個(gè)相關(guān)的命令:unt(until)。使用unt命令類似于c命令,但是它的運(yùn)行結(jié)果是,下一行比當(dāng)前代碼行數(shù)大的位置。有時(shí),unt更加方便和便捷使用,讓我們?cè)谙旅姘咐姓故?,首先給出使用語(yǔ)法:

取決于我們是否輸入代碼行數(shù)參數(shù)lineno,unt命令可以按照下面兩種方式運(yùn)行:

  • 沒有l(wèi)ineno,代碼可以繼續(xù)執(zhí)行到,下一次行數(shù)比當(dāng)前行數(shù)大的位置,這就相似于n(next),也就是類似于執(zhí)行“step over”的另一種形式。但是,命令n和unt的不同點(diǎn)是,unt只會(huì)在下一次行數(shù)比當(dāng)前行數(shù)大的位置停止,而n命令會(huì)停在下一個(gè)邏輯執(zhí)行行。
  • 具有l(wèi)ineno,代碼會(huì)運(yùn)行到下一次行數(shù)比當(dāng)前行數(shù)大或者相等的位置,這就類似于c(continue)后面帶有一個(gè)代碼函數(shù)參數(shù)。

上面兩種情況下,unt命令類似于n(next)和s(step)都只會(huì)停止在當(dāng)前幀(或函數(shù))。

當(dāng)你想繼續(xù)執(zhí)行并在當(dāng)前源文件中更遠(yuǎn)的地方停止時(shí),請(qǐng)使用 unt。 你可以將其視為 n (next) 和 b (break) 的混合體,具體取決于是否傳遞行號(hào)參數(shù)。

在下面的示例中,有一個(gè)帶有循環(huán)的函數(shù)。 在這里,我們希望繼續(xù)執(zhí)行代碼并在循環(huán)后停止,而不是單步執(zhí)行循環(huán)的每次迭代或設(shè)置斷點(diǎn),下面是文件codeExample4unt.py文件的內(nèi)容:

#!/usr/bin/env python3

import os
def get_path(fname):
    """Return file's path or empty string if no path."""
    import pdb; pdb.set_trace()
    head, tail = os.path.split(fname)
    for char in tail:
        pass  # Check filename char
    return head

filename = __file__
filename_path = get_path(filename)
print(f'path = {filename_path}')

以及命令下使用unt輸出如下:

$ ./codeExample4unt.py 
> /code/codeExample4unt.py(9)get_path()
-> head, tail = os.path.split(fname)
(Pdb) ll
  6     def get_path(fname):
  7         """Return file's path or empty string if no path."""
  8         import pdb; pdb.set_trace()
  9  ->     head, tail = os.path.split(fname)
 10         for char in tail:
 11             pass  # Check filename char
 12         return head
(Pdb) unt
> /code/codeExample4unt.py(10)get_path()
-> for char in tail:
(Pdb) 
> /code/codeExample4unt.py(11)get_path()
-> pass  # Check filename char
(Pdb) 
> /code/codeExample4unt.py(12)get_path()
-> return head
(Pdb) p char, tail
('y', 'codeExample4unt.py')

ll命令首先用于打印函數(shù)的源代碼,然后是 unt。 pdb 記得上次輸入的命令,所以我只是按 Enter 重復(fù) unt 命令。 這將繼續(xù)執(zhí)行代碼,直到到達(dá)比當(dāng)前行大的源代碼行。

請(qǐng)注意,在上面的控制臺(tái)輸出中,pdb 僅在第 10 行和第 11 行停止了一次。由于使用了 unt,因此僅在循環(huán)的第一次迭代中停止執(zhí)行。 但是,循環(huán)的每次迭代都被執(zhí)行。 這可以在輸出的最后一行進(jìn)行驗(yàn)證。 char 變量的值 'y' 等于 tail 值 'codeExample4unt.py' 中的最后一個(gè)字符。

6.顯示表達(dá)式

類似于打印表達(dá)式p和pp的功能,我們可以使用dispaly [expression]來告訴pdb來顯示一個(gè)表達(dá)的值,同樣適用undisplay [expression]來清楚一個(gè)表達(dá)式顯示,下面使用一些使用語(yǔ)法解釋:

下面是一個(gè)案例,代碼文件為codeExample4display.py,展示了一個(gè)循環(huán)的使用:

$ ./codeExample4display.py 
> /code/codeExample4display.py(9)get_path()
-> head, tail = os.path.split(fname)
(Pdb) ll
  6     def get_path(fname):
  7         """Return file's path or empty string if no path."""
  8         import pdb; pdb.set_trace()
  9  ->     head, tail = os.path.split(fname)
 10         for char in tail:
 11             pass  # Check filename char
 12         return head
(Pdb) b 11
Breakpoint 1 at /code/codeExample4display.py:11
(Pdb) c
> /code/codeExample4display.py(11)get_path()
-> pass  # Check filename char
(Pdb) display char
display char: 'e'
(Pdb) c
> /code/codeExample4display.py(11)get_path()
-> pass  # Check filename char
display char: 'x'  [old: 'e']
(Pdb) 
> /code/codeExample4display.py(11)get_path()
-> pass  # Check filename char
display char: 'a'  [old: 'x']
(Pdb) 
> /code/codeExample4display.py(11)get_path()
-> pass  # Check filename char
display char: 'm'  [old: 'a']

在上面的輸出中,pdb 自動(dòng)顯示了 char 變量的值,因?yàn)槊看斡龅綌帱c(diǎn)時(shí),它的值都會(huì)發(fā)生變化。 有時(shí)這很有幫助并且正是你想要的,但還有另一種使用顯示的方法。

你可以多次輸入 display 以構(gòu)建表達(dá)式監(jiān)視列表。 這比 p 更容易使用。 添加你感興趣的所有表達(dá)式后,只需輸入 display 即可查看當(dāng)前值:

$ ./codeExample4display.py 
> /code/codeExample4display.py(9)get_path()
-> head, tail = os.path.split(fname)
(Pdb) ll
  6     def get_path(fname):
  7         """Return file's path or empty string if no path."""
  8         import pdb; pdb.set_trace()
  9  ->     head, tail = os.path.split(fname)
 10         for char in tail:
 11             pass  # Check filename char
 12         return head
(Pdb) b 11
Breakpoint 1 at /code/codeExample4display.py:11
(Pdb) c
> /code/codeExample4display.py(11)get_path()
-> pass  # Check filename char
(Pdb) display char
display char: 'e'
(Pdb) display fname
display fname: './codeExample4display.py'
(Pdb) display head
display head: '.'
(Pdb) display tail
display tail: 'codeExample4display.py'
(Pdb) c
> /code/codeExample4display.py(11)get_path()
-> pass  # Check filename char
display char: 'x'  [old: 'e']
(Pdb) display
Currently displaying:
char: 'x'
fname: './codeExample4display.py'
head: '.'
tail: 'codeExample4display.py'

7.Python調(diào)用者ID

在最后一節(jié),我們會(huì)基于上述學(xué)習(xí)到的內(nèi)容,來演示“call ID”的功能使用。下面是案例代碼codeExample5.py的內(nèi)容:

#!/usr/bin/env python3
import fileutil
def get_file_info(full_fname):
    file_path = fileutil.get_path(full_fname)
    return file_path
filename = __file__
filename_path = get_file_info(filename)
print(f'path = {filename_path}')

以及工具模塊fileutil.py文件內(nèi)容:

def get_path(fname):
    """Return file's path or empty string if no path."""
    import os
    import pdb; pdb.set_trace()
    head, tail = os.path.split(fname)
    return head

在這種情況下,假設(shè)有一個(gè)大型代碼庫(kù),其中包含一個(gè)實(shí)用程序模塊 get_path() 中的函數(shù),該函數(shù)使用無效輸入進(jìn)行調(diào)用。 但是,它是從不同包中的許多地方調(diào)用的。

如何查看調(diào)用程序是誰?

使用命令w(where)來打印一個(gè)代碼棧序列,就是從低到上顯示所有堆棧:

$ ./codeExample5.py 
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) w
  /code/codeExample5.py(12)module>()
-> filename_path = get_file_info(filename)
  /code/codeExample5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) 

如果這看起來令人困惑,或者你不確定堆棧跟蹤或幀是什么,請(qǐng)不要擔(dān)心。 我將在下面解釋這些術(shù)語(yǔ)。 這并不像聽起來那么困難。

由于最近的幀在底部,從那里開始并從下往上閱讀。 查看以 -> 開頭的行,但跳過第一個(gè)實(shí)例,因?yàn)?pdb.set_trace() 用于在函數(shù) get_path() 中輸入 pdb。 在此示例中,調(diào)用函數(shù) get_path() 的源代碼行是:

-> file_path = fileutil.get_path(full_fname)

在每個(gè) -> 上面的行包含文件名和代碼行數(shù)(在括號(hào)中),以及函數(shù)名稱,所以調(diào)用者就是:

 /code/example5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)

顯然在這個(gè)簡(jiǎn)單的樣例中,我們展示了如何查找函數(shù)調(diào)用者,但是想象一個(gè)大型應(yīng)用程序,您在其中設(shè)置了一個(gè)帶有條件的斷點(diǎn),以確定錯(cuò)誤輸入值的來源,下面讓我們進(jìn)一步深入。

什么是堆棧追蹤和棧幀內(nèi)容?

堆棧跟蹤只是 Python 為跟蹤函數(shù)調(diào)用而創(chuàng)建的所有幀的列表。 框架是 Python 在調(diào)用函數(shù)時(shí)創(chuàng)建并在函數(shù)返回時(shí)刪除的數(shù)據(jù)結(jié)構(gòu)。 堆棧只是在任何時(shí)間點(diǎn)的幀或函數(shù)調(diào)用的有序列表。 (函數(shù)調(diào)用)堆棧在應(yīng)用程序的整個(gè)生命周期中隨著函數(shù)被調(diào)用然后返回而增長(zhǎng)和縮小。打印時(shí),這個(gè)有序的幀列表,即堆棧,稱為堆棧跟蹤。 你可以隨時(shí)通過輸入命令 w 來查看它,就像我們?cè)谏厦嬲业秸{(diào)用者一樣。

當(dāng)前棧幀什么意思?

將當(dāng)前幀視為 pdb 已停止執(zhí)行的當(dāng)前函數(shù)。 換句話說,當(dāng)前幀是你的應(yīng)用程序當(dāng)前暫停的位置,并用作 pdb 命令(如 p(打?。┑摹皡⒖紟?。p 和其他命令將在需要時(shí)使用當(dāng)前幀作為上下文。 在 p 的情況下,當(dāng)前幀將用于查找和打印變量引用。當(dāng) pdb 打印堆棧跟蹤時(shí),箭頭 > 表示當(dāng)前幀。

怎么使用和切換棧幀?

你可以使用兩個(gè)命令 u(向上)和 d(向下)來更改當(dāng)前幀。 與 p 結(jié)合使用,這允許你在任何幀中調(diào)用堆棧的任何點(diǎn)檢查應(yīng)用程序中的變量和狀態(tài)。使用語(yǔ)法如下:

讓我們看一個(gè)使用 u 和 d 命令的例子。 在這種情況下,我們要檢查codeExample5.py 中函數(shù) get_file_info() 的局部變量 full_fname。 為此,我們必須使用命令 u 將當(dāng)前幀向上更改一級(jí):

$ ./codeExample5.py 
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) w
  /code/codeExample5.py(12)module>()
-> filename_path = get_file_info(filename)
  /code/codeExample5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) u
> /code/codeExample5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)
(Pdb) p full_fname
'./codeExample5.py'
(Pdb) d
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) p fname
'./codeExample5.py'
(Pdb) 

因?yàn)槭窃趂ileutil.py文件函數(shù)get_path()中設(shè)置的斷點(diǎn)程序pdb.set_trace(),所以當(dāng)前幀被設(shè)置在此處,如下面所示:

> /code/fileutil.py(5)get_path()

為了訪問和打印在codeExample5.py文件函數(shù)get_file_info()里的變量full_fname,命令u實(shí)現(xiàn)移動(dòng)到前一個(gè)棧幀:

(Pdb) u
> /code/codeExample5.py(7)get_file_info()
-> file_path = fileutil.get_path(full_fname)

請(qǐng)注意,在上面 u 的輸出中,pdb 在第一行的開頭打印了箭頭 >。 這是 pdb,讓你知道幀已更改,并且此源位置現(xiàn)在是當(dāng)前幀。 現(xiàn)在可以訪問變量 full_fname。 另外,重要的是要意識(shí)到第 2 行以 -> 開頭的源代碼行已被執(zhí)行。 由于幀被移到堆棧上,fileutil.get_path() 已被調(diào)用。 使用 u,我們將堆棧向上移動(dòng)(從某種意義上說,回到過去)到調(diào)用 fileutil.get_path() 的函數(shù) codeExample5.get_file_info()。

繼續(xù)這個(gè)例子,在打印 full_fname 之后,使用 d 將當(dāng)前幀移動(dòng)到其原始位置,并打印 get_path() 中的局部變量 fname。如果我們?cè)敢?,我們可以通過將 count 參數(shù)傳遞給 u 或 d 來一次移動(dòng)多個(gè)幀。 例如,我們可以通過輸入 u 2 移動(dòng)到 codeExample5.py 中的模塊級(jí)別:

$ ./codeExample5.py 
> /code/fileutil.py(5)get_path()
-> head, tail = os.path.split(fname)
(Pdb) u 2
> /code/codeExample5.py(12)module>()
-> filename_path = get_file_info(filename)
(Pdb) p filename
'./codeExample5.py'
(Pdb) 

當(dāng)你調(diào)試和思考許多不同的事情時(shí),很容易忘記你在哪里。 請(qǐng)記住,你始終可以使用恰當(dāng)命名的命令 w (where) 來查看執(zhí)行暫停的位置以及當(dāng)前幀是什么。

8.總結(jié)

本篇教程中,我們主要講解了pdb中一些基本常用的內(nèi)容:

打印表達(dá)式使用n(next)和s(step)命令調(diào)試代碼斷點(diǎn)使用使用unt(until)來繼續(xù)執(zhí)行代碼顯示表達(dá)式查找一個(gè)函數(shù)的調(diào)用者

最后,希望這篇教程對(duì)你帶來幫助,本篇教程中對(duì)應(yīng)的代碼可以在代碼倉(cāng)庫(kù)中下載:https://github.com/1311440131/pdb_basic_tutorial

到此這篇關(guān)于如何使用pdb進(jìn)行Python調(diào)試的文章就介紹到這了,更多相關(guān)pdb Python調(diào)試內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:
  • python的pdb調(diào)試命令的命令整理及實(shí)例
  • 總結(jié)用Pdb庫(kù)調(diào)試Python的方式及常用的命令
  • 使用pdb模塊調(diào)試Python程序?qū)嵗?/li>
  • 使用PDB簡(jiǎn)單調(diào)試Python程序簡(jiǎn)明指南
  • 使用Python中PDB模塊中的命令來調(diào)試Python代碼的教程
  • python pdb調(diào)試方法分享

標(biāo)簽:沈陽(yáng) 紅河 新疆 長(zhǎng)治 河南 上海 滄州 樂山

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《如何使用pdb進(jìn)行Python調(diào)試》,本文關(guān)鍵詞  ;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無關(guān)。
  • 相關(guān)文章
  • 收縮
    • 微信客服
    • 微信二維碼
    • 電話咨詢

    • 400-1100-266