本節書摘來自異步社群《python高性能程式設計》一書中的第2章,第2.13節,作者[美] 戈雷利克 (micha gorelick),胡世傑,徐旭彬 譯,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。
如果你不對你的代碼進行單元測試,那麼從長遠來看你可能正在損害你的生産力。ian(臉紅)十分尴尬地提到有一次他花了一整天的時間優化他的代碼,因為嫌麻煩是以他禁用了單元測試,最後卻發現那個顯著的速度提升隻是因為他破壞了需要優化的那段算法。這樣的錯誤你一次都不要犯。
除了單元測試,你還應該堅定地考慮使用coverage.py。它會檢查有哪些代碼行被你的測試所覆寫并找出那些沒有被覆寫的代碼。這可以讓你迅速知道你是否測試了你想要優化的代碼,那麼在優化過程中可能潛伏的任何錯誤都會被迅速抓出來。
no-op的@profile修飾器
如果你的代碼使用了line_profiler或者memory_profiler的@profile修飾器,那麼你的單元測試會引發一個nameerror異常并失敗。原因是單元測試架構不會将@profile修飾器注入本地名字空間。no-op修飾器可以在這種時候解決問題。在你測試時把它加入你的代碼塊,并在你結束測試後移除它是在友善不過的事情了。
使用no-op修飾器,你可以運作你的測試而不需要修改你的代碼。這意味着你可以在每次優化之後都運作你的測試,你将永遠不會倒在一個出問題的優化步驟上。
如例2-20所示,假設我們有一個ex.py子產品,它有一個測試用例(基于nosetests架構)和一個函數,這個函數我們正在用line_profiler或者memory_profiler進行性能分析。
例2-20 一個簡單的函數和一個測試用例需要用到@profile
如果我們運作nosetests測試我們的代碼就會得到一個nameerror:
解決方法是在ex.py開頭添加一個no-op修飾器(你可以在完成性能分析之後移除它)。如果在名字空間中尋找不到@profile修飾器(因為沒有使用line_profiler或者memory_profiler),那麼我們寫的no-op版本的修飾器就會被加入名字空間。如果line_profiler或者memory_profiler已經将新的函數加入名字空間,那麼我們no-op版本的修飾器就會被忽略。
對于line_profiler,我們可以加入例2-21的代碼。
例2-21 在單元測試時在名字空間中加入針對line_profiler的no-op@profile修飾器
<code>__builtin__</code>檢查是針對nosetests的,hasattr則用來檢查@profile修飾器是否已經被加入名字空間。現在可以在我們的代碼上成功運作nosetests了:
對于memory_profiler,我們使用例2-22的代碼。
例2-22 在單元測試時在名字空間中加入針對memory_profiler的no-op@profile修飾器
期望産生的輸出如下:
不使用這些修飾器可以節省你幾分鐘,但是一旦你在一個破壞你代碼的錯誤優化上失去了好幾個小時,你就會想要把這個加入你的工作流程了。