print 函式已老,DeBug 該靠 PySnooper 了~
小夥伴們,你們都怎樣 DeBug Python 程式碼?是不是常用 print 大法?在本文介紹的這個專案中,deBug Python 程式碼再也不需要 print 了。只要給有疑問的程式碼加上裝飾器,各種資訊一目瞭然,找出錯誤也就非常簡單了。
這個名為 PySnooper 的專案是剛開源的,僅僅一天就獲得了 2K+ 的 Star 量,當然這「一天」還沒結束,這個收藏量也會繼續重新整理。
專案地址:https://github.com/cool-RR/pysnooper
Python 怎樣 DeBug?
如果寫著寫著模型,發現模型不 work 了,那麼你該怎樣找出 Python 的錯誤語句?這種錯誤一般與語法無關,而是某個變數的運算發生了錯誤。接下來我們就要慢慢找哪個地方有問題了,這裡最常見、最直觀的方法就是 print 大法。把我們懷疑的變數列印出來,總會找到異常的地方。
如果程式碼中嵌入了單元測試,例如 assert 語句,那麼還能縮小一些懷疑範圍。但通常我們都要多次嘗試,列印多個變數才能找到錯誤的地方。在 PyTorch 或 Keras 這樣的動態計算圖還好,列印出來的直接是一個值,像 TensorFlow 這樣的靜態計算圖,列印出來是張量資訊而不是值,這就很尷尬了。
實際上不止是機器學習,在我們寫 Python 的時候,總是想搞清楚為什麼寫的程式碼在執行時有點不大對。很多讀者樂於使用斷點等成熟的 DeBug 工具,也有的直接使用 print 大法找錯誤的地方。但現在我們不需要擔心了,本文將介紹一個新的開源工具,它信心滿滿地呼籲到:「不要再使用 print 函式來 DeBug 啦~」
極簡DeBug工具PySnooper
一般情況下,想要知道哪一行程式碼在執行、哪一行不執行、本地變數的值是多少時,大部分人會使用 print 函式,在關鍵部分列印某個或某組變數的值、形狀、型別等資訊。
而 PySnooper 讓你能快速地獲得這些資訊,且相比之下它不需要細緻地寫 print 函式,只需要向感興趣的函式增加一個裝飾器就行了。我們會得到該函式的詳細 log,包含哪行程式碼能執行、什麼時候執行以及本地變數變化的確切時間。
相比於其他程式碼智慧工具,PySnooper 為何如此優秀?因為不需要任何設定,你就可以在劣等、不規則的企業程式碼庫上使用 PySnooper。只需要加個裝飾器,併為日誌輸出地址指定路徑就行了。
這樣說可能不太直觀,下面我們可以具體看個案例,PySnooper 的優秀就能一目瞭然。
PySnooper 案例
下面專案作者寫了一個函式以將數值轉換為二進位制碼,該函式返回的是一個二進位制列表。下面我們將裝飾器 @pysnooper.snoop() 加到該函式上,就大功告成了。
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)
該函式返回的日誌如下,我們可以看到在呼叫 number_to_bits 函式時,賦予引數 number 的初始值為 6。然後,PySnooper 就還是對著原始碼一行行分析了。
如上分析所示,函式每建立一個新變數,那麼這個變數的值、這個變數的變化都會展示出來。而且 PySnooper 還將迴圈展開,因此變化的細節更加明確。最終 6 的二進位制版本應該是 [1, 1, 0],它的變化過程也展示在 bits 變數中。
現在透過這些詳細資訊,PySnooper 再也不用擔心我們用 print 函式強行 deBug 了。
PySnooper 詳細特徵
如果標準錯誤輸出難以獲得,或者太長了,那麼可以將輸出定位到本地檔案:
@pysnooper.snoop('/my/log/file.log')
檢視一些非本地變數的值:
@pysnooper.snoop(variables=('foo.bar', 'self.whatever'))
展示我們函式中呼叫函式的 snoop 行:
@pysnooper.snoop(depth=2)
將所有 snoop 行以某個字首開始,更容易定位和找到:
@pysnooper.snoop(prefix='ZZZ ')
演示 PySnooper
下面我們最開始嘗試使用 PySnooper 獲取 TensorFlow 的資訊,如果它能獲取各種張量資訊,那可就太強大了。
首先使用 pip 安裝包:
pip install pysnooper
果然,TensorFlow 這種靜態圖並不能很好地獲取資訊,讀者也可嘗試一下。後面我們試了試 NumPy,希望能獲取整個計算流的資訊。如下程式碼所示,我們建立了兩個陣列變數,並且 2×2 的矩陣會連乘多次,如果能追蹤到這種連乘,那就比較好處理錯誤。
import pysnooper
import numpy as np
@pysnooper.snoop()
def multi_matmul(times):
x = np.random.rand(2, 2)
w = np.random.rand(2, 2)
for i in range(times):
x = np.matmul(x, w)
return x
multi_matmul(3)
對於 NumPy,該工具確實能追蹤所有可疑變數的變化過程。當然在實際運算中,矩陣乘法的維度會非常大,我們可以直接追蹤形狀(Shape),而不是具體的值。