別再用print輸出來除錯程式碼了
作者介紹: changqing,騰訊互娛品質管理部Turing Lab研究員
| 導語 最近在github上冒出了一個python的debug神器PySnooper,號稱在debug時可以消滅print。那麼該工具有哪些優點呢,如何使用該工具呢。本文就介紹該工具的優缺點和使用方式。
前言:
使用python開發過程中,總是避免不了debug。傳統的debug過程大致分為兩種:
a)斷點+單步除錯。
斷點+單步除錯估計是用的最多的了,對於較大型專案來說,其流程大致為:先在關鍵的程式碼位置加上print語句,通過分析print的值將範圍縮小,這個過程可能需要重複多次,使用print的方法,一般可以將範圍縮小到一個比較完整的功能模組中;然後在可能出現bug的模組中的關鍵部分打上斷點,進入到斷點後使用單步除錯,檢視各變數的值是否正確,最後根據錯誤的變數值定位到具體的程式碼行,最後進行修改。
b) pdb除錯。
pdb是python自帶的一個包,為 python 程式提供了一種互動的原始碼除錯功能,主要特性包括設定斷點、單步除錯、進入函式除錯、檢視當前程式碼、檢視棧片段、動態改變變數的值等。pdb的除錯流程和1)基本差不多,其具體的使用方法大家可以網上搜一下。
傳統的debug的方法的缺點包括:
a)需要在程式碼中新增print語句,這就改變了原有的程式碼;
b)在斷點除錯和單步除錯過程中,需要保持持續的專注,一旦跳過了關鍵點就要從頭開始。
最近在github上冒出了一個debug工具,可以解決傳統debug過程中的缺點。下面一塊來看看這個工具的使用和神奇之處。
1. PySnooper是什麼
該工具使用採用裝飾器的形式,將函式的執行過程以日誌的形式列印到檔案中,其記錄了運行了哪些程式碼行,執行的時間及執行到當前程式碼時各變數的值。根據變數的變化就可以定位問題了。親自試用該工具後,其優點可總結為以下幾點:
1、無需為了檢視變數的值,使用print列印變數的值,從而修改了原有的程式碼。
2、介面的執行過程以日誌的形式儲存,方便隨時檢視。
3、可以根據需要,設定函式呼叫的函式的層數,方便將注意力集中在需要重點關注的程式碼段。
4、多個函式的日誌,可以設定日誌字首表示進行標識,方便檢視時過濾。
該工具有這麼多優點,那麼如何使用呢,下面結合demo來介紹該工具的使用。
2. 使用方式介紹
1. 工具安裝
pip install pysnooper
2. 官方demo介紹
官方demo程式碼:
import pysnooper
@pysnooper.snoop()
def number_to_bits(number):
if number:
bits = []
while number:
number, remainder = divmod(number, 2)
bits.insert(0, remainder)
return bits
else:
return [0]
number_to_bits(6)
控制檯輸出:
控制檯的輸出如上圖,從圖中可以看到,從進入到函式開始,會記錄每一行程式碼的執行及記錄新增區域性變數或已有區域性變數的變化,直到函式結束。以裝飾器的形式使用該工具後,會將函式執行的中間結果打印出來,這樣方便後續的bug定位和分析。
3. 引數介紹
以裝飾器的形式使用該工具,其包含了四個引數,引數包括output, variables, depth, prefix,如下圖。
1、output引數。該引數指定函式執行過程中產生的中間結果的儲存位置,若該值為空,則將中間結果輸出到控制檯。
2、variables引數。該引數是vector型別, 因為在預設情況下,裝飾器只跟蹤區域性變數,要跟蹤非區域性變數,則可以通過該欄位來指定。預設值為空vector。
3、depth引數。該引數表示需要追蹤的函式呼叫的深度。在很多時候,我們在函式中會呼叫其他函式,通過該引數就可以指定跟蹤呼叫函式的深度。預設值為1。
4、prefix引數。該引數用於指定該函式介面的中間結果字首。當多個函式都使用的該裝飾器後,會將這些函式呼叫的中間結果儲存到一個檔案中,此時就可以通過字首過濾不同函式呼叫的中間結果。預設值為空字串。
3. 工具應用
要使用該工具只需要理解該裝飾器(snoop)的引數的含義,下面結合幾個demo介紹引數的使用及對結果的影響。
1. output 引數使用
若使用預設引數,則將中間結果輸出到控制檯,若填寫該引數,則將中間結果寫入到該引數指定的目錄下,如執行以下程式碼,其中間結果會儲存在裝飾器snoop中設定日誌儲存的路徑中,注意這裡不會自動建立目錄,所以需要事先建立目錄,如測試程式碼中填寫路徑後需要建立log目錄。
測試程式碼:
import pysnooper
def add(num1, num2):
return num1 + num2
@pysnooper.snoop("./log/debug.log", prefix="--*--")
def multiplication(num1, num2):
sum_value = 0
for i in range(0, num1):
sum_value = add(sum_value, num2)
return sum_value
value = multiplication(3, 4)
執行該程式碼後,在./log/debug.log的內容如下:
從執行程式碼的中間結果中可以看出,檔案中記錄了各行程式碼的執行過程及區域性變數的變化。在debug時,通過分析該檔案,就可以跟蹤每一步的執行過程及區域性變數的變化,這樣就能快速的定位問題所在;由於執行的中間結果儲存在檔案中,方便隨時分析其執行的中間結果,也便於共享。
2. variables引數使用
在預設引數的情況下,使用該工具只能檢視局變數的變化過程,當需要檢視區域性變數以外變數時,則可以通過variables引數進行設定,比如下方程式碼,在Foo型別,需要檢視類例項的變數self.num1, self.num2, self.sum_value,則可以看將該變數設定當引數傳入snoop的裝飾器中。
測試程式碼:
import pysnooper
class Foo(object):
def __init__(self):
self.num1 = 0
self.num2 = 0
self.sum_value = 0
def add(self, num1, num2):
return num1 + num2
@pysnooper.snoop(output="./log/debug.log", variables=("self.num1", "self.num2", "self.sum_value"))
def multiplication(self, num1, num2):
self.num1 = num1
self.num2 = num2
sum_value = 0
for i in range(0, num1):
sum_value = self.add(sum_value, num2)
self.sum_value = sum_value
return sum_value
foo = Foo()
foo.multiplication(3, 4)
為了體現該引數的作用,這裡分別使用預設引數和上述引數(程式碼中設定的引數)執行程式碼,得到的結果如下:
使用預設引數的結果
使用程式碼中引數的結果
從兩個中間結果中可以看出,若變數不是區域性變數,哪怕在函式中使用了該變數,如果不顯示設定列印該變數的中間結果,則不會將該變數的中間結果列印到檔案中。
3. depth引數使用
該引數用來指定記錄函式呼叫層數的結果,預設值為1,若要檢視多層函式呼叫的中間結果,則可將該引數設定為>=2。
測試程式碼:
import pysnooper
def add(num1, num2):
return num1 + num2
@pysnooper.snoop("./log/debug.log", depth=2)
def multiplication(num1, num2):
sum_value = 0
for i in range(0, num1):
sum_value = add(sum_value, num2)
return sum_value
value = multiplication(3, 4)
為了對比,將depth的值分別設定為1和2,其結果如下:
depth=1的結果
depth=2的結果
從上述結果中可以看出,若要檢視更深層次函式呼叫的情況,則可以通過設定depth值進行檢視。這樣方便使用者有選擇性的檢視函式的呼叫情況。
4. prefix引數使用
該引數主要用於設定中間結果的字首,這樣就可以區分不同的函式呼叫的中間結果,預設引數為""。
測試程式碼:
import pysnooper
def add(num1, num2):
return num1 + num2
@pysnooper.snoop("./log/debug.log", prefix="--*--")
def multiplication(num1, num2):
sum_value = 0
for i in range(0, num1):
sum_value = add(sum_value, num2)
return sum_value
value = multiplication(3, 4)
執行程式碼後的中間結果如下:
從結果中可以看到,中間結果的每一行都包含了prefix設定的字首,這樣便於區分不同的函式呼叫的中間結果。
上述的介紹為了將注意力集中到具體的引數,採取設定單一引數的形式進行介紹(output+其他單個引數)。在實際使用時,可以同時設定多個引數。使用PySnooper工具來記錄函式執行的中間結果,比起傳統的使用斷點+單步除錯,pdb等除錯方法,PySnooper工具有著巨大的優勢。
4. 該工具的不足之處
雖然使用debug在使用PySn ooper很方便,但還是存在一些問題(以4月26號拉取程式碼為依據),比如:
1、無法很好的支援遞迴呼叫。
2、呼叫每個函式的中間結果只能儲存在一個檔案中,如果需要區分不同檔案的結果,需要使用prefix來進行字首標識。
3、對於跨檔案函式呼叫,不支援記錄呼叫函式所在的檔名。
當然PySnooper是最近在github上火起來的專案,還不夠完善是正常的,相信這些不足之處後續也會得到完善,期待一個更好的PySnooper。
5. 總結
本文介紹PpySnooper的工具,先介紹了該工具是什麼,相比傳統debug方法的優勢,然後介紹了該工具的引數及說明該引數作用的demo。最後介紹了該工具的不足之處。
參考文獻:
https://mp.weixin.qq.com/s/QayverYLWv5dU38RFYK6Ew
https://github.com/cool-RR/PySnooper