pdb為python程序實現了一個交互式調試環境。它包括一些特性,可以暫停程序,查看變量值,以及逐步監視程序執行,從而能了解程序具體做了什么,並查找邏輯中存在的bug。
啟動調試工具
使用pdb的第一步是讓解釋器在適當的時候進入調試工具。可以采用很多不同的方法
達到這個目的,具體取決於起始條件和所要調試的內容。
從命令行執行
pdb的最直接的方式是通過命令行運行,命令格式如下
python3 -m pdb py文件
這里我們來個小例子,有一個文件名為1.py,代碼如下
def foo(num):
print(f"當前的數字是:{num}")
if __name__ == '__main__':
foo(3)
然后再命令行中定位到當前位置。
通常pdb在打印一個文件名時會在輸出中包含各模塊的完整路徑,然后會顯示出下一行將要運行的代碼。這個里的話就是接下來將運行函數foo了
在解釋器中運行
如果在交互式環境下運行調試工具,可以使用run()或者runeval()。
在程序中運行
上面的情況只是適合一開始就啟動調試工具,對於長時間的而且需要運行一段時間才去進行調試的情況,更方便的做法就是在需要運行的代碼之前加set_trace()方法了。
set_trace()可以在任意位置進行調用,像下面的例子。
import pdb
def foo(num):
print(f"當前的數字是:{num}")
for i in range(num):
pdb.set_trace()
print(f"當前循環的數字:{i}")
if __name__ == '__main__':
foo(10)
上面的代碼在for循環中打了一個斷點,可以發現我們程序停在了for循環的位置,然后顯示出下一行將要執行的內容
print(f"當前循環的數字:{i}")
怎么知道循環到第幾個數字呢?
答:通過直接輸入變量i可以看到當前的值。
怎么知道當前的代碼運行到哪了?
答:通過命令where(簡寫w)可以得出正在執行哪一行,以及程序的調用棧的位置。
如何查看附近當然代碼附近的代碼信息?
答:通過list(簡寫l)可以看周圍的代碼默認是上5行、下5行。
另外命令longlist(簡寫ll)可以輸出當前的函數的源碼。
如果需要看整個類的源碼可以通過source+類名獲取源碼。
常用命令
步驟執行
step(簡稱s):函數單步執行,如果遇到函數會進入函數內部繼續調試,如果不需要進入函數體只是一步一步執行,此時就要用下面的next了。
next(簡稱n):單步執行命令,不會進入函數體,但是向之前說的那個如果遇到了一個for循環10次還好如果是10000次呢,此時就要用到下面的命令了。
until:該命令類型next,只不過它會繼續執行,直到執行到同一個函數中行號大於當前值的一行,也就是說可以用until跳出循環末尾。當然until也可以指定一個比當前行號大的值,調到指定位置。
一個例子
import pdb
class Myobj():
def foo(self,num):
print(f"當前的數字是:{num}")
pdb.set_trace()
for i in range(num):
print(f"當前循環的數字:{i}")
print("over")
if __name__ == '__main__':
m=Myobj()
m.foo(4)
可以發現執行until把整個循環走了一遍,然后到下一行也就是
-> print("over")
return:return也可以繞開一段代碼的捷徑,只不過它會繼續執行,直至函數准備執行一個return語句,然后會暫停,使得在函數返回之前可以看到返回值.
一個沒什么實際用途的例子,不過可以很好地演示這個效果
import pdb
lst=[]
class Myobj():
def foo(self,num):
print(f"當前的數字是:{num}")
pdb.set_trace()
for i in range(num):
print(f"當前循環的數字:{i}")
lst.append(i)
return lst
if __name__ == '__main__':
m=Myobj()
m.foo(4)
斷點相關
break(簡稱b): 當然隨着代碼的增長即使使用return和until或者next都很費時間,此時就要考慮在指定位置設置斷點的方式了,如果要在文件的一個特定行設置斷點,可以使用break lineno,然后通過下面的continue(簡寫c)命令調到下一個斷點。
我們還可以指定在某個函數中設置斷點比如:break Myobj().foo
除此之外還可以執行其他文件設置斷點,也可以相當於sys.path上將某個文件的相對路徑。如果只執行break命令可以看到哪些地方有斷點,包括哪個文件行號等信息。
disable:可以指定上面break之后顯示的斷點,執行后可以發現之前Enb欄有yes變為false。此時輸入l可以看到打斷點的為會有B標識。
如果想徹底刪除就需要執行clear命令了。
)
clear:徹底刪除一個斷點,使用方式clear id號,類型disable
tbreak:臨時斷點,程序第一次執行到臨時斷點時會自動清除。不用再去手動刪除了。
條件斷點
可以對斷點應用一些規則,以便其僅當條件滿足時才執行。與手動啟用和禁用斷點相比,使用條件斷點可以更好地控制調試器暫停程序的方式。條件斷點可以通過兩種方式設置。第一種方法是指定使用break設置斷點時的條件。使用方法是代碼行號加表達式。看一個應用例子
import pdb
lst = []
class Myobj():
pdb.set_trace()
def foo(self, num):
print(f"當前的數字是:{num}")
if __name__ == '__main__':
m = Myobj()
[m.foo(i) for i in range(10)]
解析下圖中命令的含義:
1.break 10,num>5,是指在第10行打斷點,然后條件是num>5的時候,通過
后面輸出break可以看到具體的斷點信息,很明顯看到我們的斷點條件
stop only if num>5
2.如果表達式的計算結果為true,則執行將在斷點處停止。
除此之外,還可以使用條件命令將條件應用於現有斷點。參數是斷點id和表達式。
還是上面的代碼讓我們看效果圖。
忽略斷點
如果在循環的過程中想忽略前幾條結果,比如這里忽略前3個,就可以使用ignore.
使用方法是:
ignore 斷點id 忽略次數。
如果在運行之前不想忽略了可以使用下面命令,如果已經運行continue了的話就沒效果了。
ignore 斷點id 0
監視變量
display:有時候我們需要實時觀察一個變量的變化,這個時候dispaly就是最好的幫手,如果想移出可以使用undisplay。
改變工作流
jump:jump命令在運行時改變程序的流程,而不修改代碼。 它可以向前跳過以避免運行某些代碼,也可以向后跳轉以再次運行它。
import pdb
def f(n):
pdb.set_trace()
result = []
j = 0
for i in range(n):
j = i * n + j
j += n
result.append(j)
return result
if __name__ == '__main__':
print(f(5))
向前跳
向前跳轉會將執行點移動到當前位置之后,而不會執行期間任何語句。
向后跳
跳轉還可以將程序執行移動到已經執行的語句中,以便再次運行它。
不允許的jump方式
1.跳入和跳出某些流控制語句,無法判斷什么時候進入。
2.跳轉可以用來輸入函數,但是不給參數,代碼也不能工作。
3.跳轉不會進入for循環或try:except語句等塊的中間。
4.finally塊中的代碼必須全部執行,因此跳轉不會離開該塊。
5.最基本的限制是跳轉被限制在調用堆棧的底部框架上。 向上移動堆棧以檢查變量后,此時無法更改執行流程。
其他命令
up(簡稱u):可以向棧中較舊的幀移動
down(簡稱d):可以向棧中較新的幀移動
每次在棧中上移或者下移時,調試工具都會打印當前位置,格式與where生成的格式相同。
args(簡稱a):可以打印當前函數的所有參數的值。
p和pp:這兩個是類似python的print和pprint的功能。輸出信息的,pp帶有美化功能。
!:在一個表達式前面加一個!,可以修改python程序當前正在運行的值,比如上面的例子num等於10,如果執行!num=3,后面再輸出num你會看到此時的num變成3了,這個可以減少測試時候我們的循環次數了。當然這個循環次數是否可以變動還是看程序的邏輯。
參考資料
https://docs.python.org/3.7/library/pdb.html
更多內容關注公眾號:python學習開發