關於python調試大法的信息

本文目錄一覽:

pdb python 調試 怎麼用

 本文章講述了如何用pdb進行python調試講解.

當手邊

沒有ide,面對著python調試犯愁時,你就可以參考下本文;(pdb 命令調試)

和 (pdb)help

用pdb進行python調試,用法基本和gdb差不多,

先看一個簡單的例子:

epdb1.py .# epdb1.py — experiment with the Python debugger, pdb

a = “aaa”

b = “bbb”

c = “ccc”

final = a + b + c

print final

比如要對這個程序進行調試:

1:在文件前面加上這一句,引入調試的模塊。

import pdb

2:在要開始調試的一行加上pdb.set_trace()文件變成:

# epdb1.py — experiment with the Python debugger, pdb

import pdb

a = “aaa”

pdb.set_trace()

b = “bbb”

c = “ccc”

final = a + b + c

print final 可以運行這個程序,到斷點出會停下來,和gdb類似,

可以執行命令:

直接回車是重複前一條命令!

p(print) 查看一個變數值

n(next) 下一步

s(step) 單步,可進入函數

c(continue)繼續前進

l(list)看源代碼

用pdb調試有多種方式可選:

1.命令行啟動目標程序,加上-m參數,這樣調用myscript.py的話斷點就是程序的執行第一行之前

python -m pdb myscript.py

2. 在Python交互環境中啟用調試

import pdb

import mymodule

pdb.run(‘mymodule.test()’)

3.比較常用的,就是在程序中間插入一段程序,相對於在一般ide裡面打上斷點然後啟動debug,不過這種方式是hardcode的 if __name__ == “__main__” :

a = 1

importpdb

pdb .set_trace()

b = 2

c = a + b

print( c)

然後正常運行腳本,到了pdb.set_trace()那就會定下來,就可以看到調試的提示符(Pdb)了

常用的調試命令 h(elp),會列印當前版本Pdb可用的命令,如果要查詢某個命令,可以輸入 h [command],例如:”h l” – 查看list命令

l(ist),可以列出當前將要運行的代碼塊

(Pdb) l

497 pdb.set_trace()

498 base_data = {}

499 new_data = {}

500 try:

501 execfile(base_file_name,{},base_data)

502 – execfile(new_file_name,{},new_data)

503 except:

504 logger.writeLog(“error! load result log error!”)

505 print “load cmp logs error!”

506 raise Exception, “load cmp logs error!”

507斷點設置

(Pdb)b10 #斷點設置在本py的第10行

或(Pdb)bots.py:20 #斷點設置到 ots.py第20行

刪除斷點(Pdb)b #查看斷點編號

(Pdb)cl 2 #刪除第2個斷點

運行

(Pdb)n #單步運行

(Pdb)s #細點運行 也就是會下到,方法

(Pdb)c #跳到下個斷點

查看

(Pdb)p param #查看當前 變數值

(Pdb)l #查看運行到某處代碼

(Pdb)a #查看全部棧內變數 b(reak), 設置斷點,例如 “b 77″,就是在當前腳本的77行打上斷點,還能輸入函數名作為參數,斷點就打到具體的函數入口,如果只敲b,會顯示現有的全部斷點

(Pdb) b 504

Breakpoint 4 at /home/jchen/regression/regressionLogCMP.py:504 condition bpnumber [condition],設置條件斷點,下面語句就是對第4個斷點加上條件”a==3″

(Pdb) condition 4 a==3

(Pdb) b

num Type Disp Enb Where

4 breakpoint keep yes at /home/jchen/regression/regressionLogCMP.py:504

stop only if a==3 cl(ear),如果後面帶有參數,就是清除指定的斷點(我在Python2.4上從來沒成功過!!!);如果不帶參數就是清除所有的斷點

(Pdb) cl

clear all breaks? y disable/enable,禁用/激活斷點

(Pdb) disable 3

(Pdb) b

num Type Disp Enb Where

3 breakpoint keep no at /home/jchen/regression/regressionLogCMP.py:505 n(ext),讓程序運行下一行,如果當前語句有一個函數調用,用n是不會進入被調用的函數體中的

s(tep),跟n相似,但是如果當前有一個函數調用,那麼s會進入被調用的函數體中

c(ont(inue)),讓程序正常運行,直到遇到斷點

j(ump),讓程序跳轉到指定的行數

(Pdb) j 497

/home/jchen/regression/regressionLogCMP.py(497)com pareLog()

– pdb.set_trace() a(rgs),列印當前函數的參數

(Pdb) a

_logger =

_base = ./base/MRM-8137.log

_new = ./new/MRM-8137.log

_caseid = 5550001

_toStepnum = 10

_cmpMap = {‘_bcmpbinarylog’: ‘True’, ‘_bcmpLog’: ‘True’, ‘_bcmpresp’: ‘True’} p,最有用的命令之一,列印某個變數

(Pdb) p _new

u’./new/MRM-8137.log’ !,感嘆號後面跟著語句,可以直接改變某個變數

q(uit),退出調試

==============================================================================================

在python中使用pdb模塊可以進行調試

import pdb

pdb.set_trace()

也可以使用python -m pdb mysqcript.py這樣的方式

(Pdb) 會自動停在第一行,等待調試,這時你可以看看 幫助

(Pdb) h

說明下這幾個關鍵 命令

斷點設置

(Pdb)b 10 #斷點設置在本py的第10行

或(Pdb)b ots.py:20 #斷點設置到 ots.py第20行

刪除斷點(Pdb)b #查看斷點編號

(Pdb)cl 2 #刪除第2個斷點

運行

(Pdb)n #單步運行

(Pdb)s #細點運行 也就是會下到,方法

(Pdb)c #跳到下個斷點

查看

(Pdb)p param #查看當前 變數值

(Pdb)l #查看運行到某處代碼

(Pdb)a #查看全部棧內變數

(Pdb)w 列出目前call stack 中的所在層。

(Pdb)d 在call stack中往下移一層

(Pdb)u 在call stack中往上移一層。如果在上移一層之後按下 n ,則會在上移之後的一層執行下一個敘述,之前的 function call 就自動返回。

(Pdb)cl 清除指定的斷點。如果沒有帶參數,則清除所有斷點。

(Pdb)disable 取消所有斷點的功能,但仍然保留這些斷點。

(Pdb)enable 恢復斷點的功能。

(Pdb)ignore 設定斷點的忽略次數。如果沒指定 count,其初始 為 0。當 count 為 0 時,斷點會正常動作。若有指定 count,則每次執行到該中斷, count 就少 1,直到 count 數為 0。

(Pdb)condition bpnumber [condition]

(Pdb)j(ump) lineno. 跳到某行執行。只有在 call stack 的最底部才能作用。

(Pdb)l 列出目前所在檔案中的位置。連續地 l 命令會一直列到檔案結尾,可以使用指定行數或範圍來列印。

(Pdb)pp 和 p 命令類似,但是使用 pprint module(沒用過 pprint,詳情請參考 Python Library Reference)。

(Pdb)alias 以一個”別名”代替”一群除錯命令”,有點類似 c/c 的 macro(詳情請參考 Python Library Reference)。

(Pdb)unalias 取消某個 alias。

(Pdb)[!]statement 在目前的環境(context)中執行敘述。

怎麼用python的pdb模塊進行調試?

工具/材料

電腦,python環境

01

首先打開電腦後,打開終端,我這裡以調試debug.py文件做說明,簡單介紹python的pdb調試。為了演示,先用cat命令查看一下debug.py的內容。

02

我這裡用的python3的環境,在終端里輸入如圖顯示python3 -m pdb debug.py命令。就是就是用python的pdb模塊調試debug.py文件代碼。

03

進入調試後,在終端里輸入小寫字母l,就是英文單詞list的縮寫,意思就是列出代碼內容。如果顯示。

04

在終端里輸入小寫字母n,就是英文單詞next的縮寫,意思就是執行下一行代碼。

05

在終端里輸入小寫字母p x,p就是英文單詞print的縮寫,意思就是列印變數x的值。

06

在終端里輸入小寫字母s,s就是英文單詞s的縮寫,進入函數內部調試。

07

在終端里輸入小寫字母a,a就是英文單詞arguments(參數)的縮寫,會列印顯示函數所有變數的值。

08

在終端里輸入小寫字母c,就是英文單詞continue的縮寫,意思就是繼續執行代碼一直結束,然後重新進入調試。

09

在終端里輸入小寫字母b和阿拉伯數字6,b就是英文單詞break的縮寫,意思就是在第6行代碼打個斷點。

10

在終端里輸入小寫字母q,q就是英文單詞quit的縮寫,意思就是退出調試。

python如何一步步調試

裝個Pycharm

1 添加斷點

2 Debug下運行代碼:

3 F8:進行下一步操作

F7 :跳入下一個方法中

vscode如何調試python

初始化配置

配置在調試會話期間驅動VS Code的行為。 配置在launch.json文件中定義,該文件存儲在工作區的.vscode文件夾中。

注意為了更改調試配置,您的代碼必須存儲在一個文件夾中。

要使用Python配置生成launch.json文件,請執行以下步驟:

1.選擇設置按鈕(在上圖中圈出)或使用Debug Open configurations菜單命令。

2.將從命令選項板打開配置菜單,允許您為打開的文件選擇所需的調試配置類型。 現在,在出現的Select a debug configuration菜單中,選擇Python File。

注意通過調試面板啟動調試會話,F5或調試啟動調試,如果不存在配置,也會打開調試配置菜單。

然後,Python擴展創建並打開一個launch.json文件,該文件包含基於您之前選擇的預定義配置,在本例中為Python文件。 您可以修改配置(例如,添加參數),還可以添加自定義配置。

更多的配置

默認情況下,VS Code僅顯示Python擴展提供的最常見配置。 您可以使用列表和launch.json編輯器中顯示的「添加配置」命令選擇要包含在launch.json中的其他配置。 當您使用該命令時,VS Code會提示您所有可用配置的列表(請務必向下滾動以查看所有Python選項):

選擇Node.js:Gulp任務會產生以下結果:

在調試過程中,狀態欄顯示左下方的當前配置; 右邊是當前的調試解釋器。 選擇配置會顯示一個列表,您可以從中選擇不同的配置:

默認情況下,調試器使用與VS Code的其他功能相同的python.pythonPath工作空間設置。 要使用不同的解釋器進行特定的調試,請在launch.json中為pythonPath設置適用的調試器配置,如下一節所述。 或者,選擇狀態欄上的命名解釋器以選擇另一個更新python.pythonPath。

設置配置選項

首次創建launch.json時,有兩種標準配置在編輯器中的集成終端(VS代碼內部)或外部終端(VS代碼外部)中運行活動文件:

具體設置將在以下部分中介紹。 您還可以添加標準配置中未包含的其他設置,例如args。

name

提供VS Code下拉列表中顯示的調試配置的名稱。

type

標識要使用的調試器類型; 用於Python代碼。

request

指定調試的模式

launch:指定調試起始文件program

attach:指定調試掛載進程

program

提供python程序的入口模塊(啟動文件)的完全限定路徑。 值:${file}, 常用於默認配置,使用編輯器中當前活動的文件。 通過指定特定的啟動文件,無論打開哪個文件,您始終可以確保使用相同的入口點啟動程序。 例如:

“program”: “/Users/Me/Projects/PokemonGo-Bot/pokemongo_bot/event_handlers/__init__.py”,

您還可以依賴工作區根目錄中的相對路徑。 例如,如果是根「/Users/Me/Projects/PokemonGo-Bot」,你可以像這樣使用

“program”: “${workspaceFolder}/pokemongo_bot/event_handlers/__init__.py”,

pythonPath

指向用於調試的Python解釋器,它可以是包含Python解釋器的文件夾。 該值可以使用變數${workspaceFolder}和${workspaceFolder}/.venv如果未指定,則此設置默認為在中標識的解釋器python.pythonPath,

或者,您可以使用在每個平台上定義的自定義環境變數來包含要使用的Python解釋器的完整路徑,這樣就不需要其他文件夾路徑。

args

指定傳遞給Python程序的參數。 由空格分隔的參數字元串的每個元素都應包含在引號內,例如:

“args”: [“–quiet”, “–norepeat”, “–port”, “1593”],

stopOnEntry

設置為true的時候,打破正在調試的程序的第一行的調試器。 如果省略(默認值)或設置為false,調試器將程序運行到第一個斷點。

console

指定程序輸出的顯示方式。

cwd

指定調試器的當前工作目錄,該目錄是代碼中使用的任何相對路徑的基本文件夾。 如果省略,則默認為${workspaceFolder}vscode的工作目錄,作為一個例子${workspaceFolder}包含了python代碼文件夾或者文件,包含了app.py

配置如下:

redirectOutput

省略或設置為時true(默認值),使調試器將程序的所有輸出列印到VS Code調試輸出窗口。 如果設置為false,程序輸出不會顯示在調試器輸出窗口中。

使用時通常禁用此選項

“console”: “integratedTerminal”

“console”: “externalTerminal”

因為不需要在調試控制台中複製輸出。

justMyCode

省略或設置為true(默認值),僅將調試限制為用戶編寫的代碼。 調成false還可以調試標準庫函數。

django

可以調試django框架

env

為調試器進程設置可選的環境變數,而不是調試器始終繼承的系統環境變數。

envFile

包含環境變數定義的文件的可選路徑。 請參閱配置Python環境 – 環境變數定義文件。

在代碼中調用斷點

在Python代碼中,您可以調用斷點 在調試會話期間要暫停調試器的任何位置。

斷點驗證

Python擴展自動檢測在非可執行行上設置的斷點,例如 通過 語句或多行語句的中間。 在這種情況下,運行調試器會將斷點移動到最近的有效行,以確保代碼執行在此時停止。

附加到本地腳本

在某些情況下,您需要調試由另一個進程在本地調用的Python腳本。 例如,您可能正在調試為特定處理作業運行不同Python腳本的Web伺服器。 在這種情況下,您需要在啟動後將VS Code調試器附加到腳本:

1.運行VS Code,打開包含腳本的文件夾或工作區,然後創建一個launch.json 對於該工作空間,如果尚不存在。

2.在腳本代碼中,添加以下內容並保存文件:

3.使用終端打開終端:創建新的集成終端,激活腳本的選定環境。在終端中,使用python -m pip install –upgrade ptvsd安裝ptvsd軟體包。

4.在終端中,使用腳本啟動Python,例如python3 myscript.py。 您應該看到代碼中包含的「等待調試器附加」消息,並且腳本在ptvsd.wait_for_attach()調用時停止。

5.切換到Debug視圖,從Debugger下拉列表中選擇Python:Attach,然後啟動調試器。

python學習網,免費的在線學習python平台,歡迎關注!

後端編程Python3-調試、測試和性能剖析(下)

單元測試(Unit Testing)

為程序編寫測試——如果做的到位——有助於減少bug的出現,並可以提高我們對程序按預期目標運行的信心。通常,測試並不能保證正確性,因為對大多數程序而言, 可能的輸入範圍以及可能的計算範圍是如此之大,只有其中最小的一部分能被實際地進 行測試。儘管如此,通過仔細地選擇測試的方法和目標,可以提高代碼的質量。

大量不同類型的測試都可以進行,比如可用性測試、功能測試以及整合測試等。這裡, 我們只講單元測試一對單獨的函數、類與方法進行測試,確保其符合預期的行為。

TDD的一個關鍵點是,當我們想添加一個功能時——比如為類添加一個方法—— 我們首次為其編寫一個測試用例。當然,測試將失敗,因為我們還沒有實際編寫該方法。現在,我們編寫該方法,一旦方法通過了測試,就可以返回所有測試,確保我們新添加的代碼沒有任何預期外的副作用。一旦所有測試運行完畢(包括我們為新功能編寫的測試),就可以對我們的代碼進行檢查,並有理有據地相信程序行為符合我們的期望——當然,前提是我們的測試是適當的。

比如,我們編寫了一個函數,該函數在特定的索引位置插入一個字元串,可以像下面這樣開始我們的TDD:

def insert_at(string, position, insert):

“””Returns a copy of string with insert inserted at the position

string = “ABCDE”

result =[]

for i in range(-2, len(string) + 2):

… result.append(insert_at(string, i,「-」))

result[:5]

[‘ABC-DE’, ‘ABCD-E’, ‘-ABCDE’,’A-BCDE’, ‘AB-CDE’]

result[5:]

[‘ABC-DE’, ‘ABCD-E’, ‘ABCDE-‘, ‘ABCDE-‘]

“””

return string

對不返回任何參數的函數或方法(通常返回None),我們通常賦予其由pass構成的一個suite,對那些返回值被試用的,我們或者返回一個常數(比如0),或者某個不變的參數——這也是我們這裡所做的。(在更複雜的情況下,返回fake對象可能更有用一一對這樣的類,提供mock對象的第三方模塊是可用的。)

運行doctest時會失敗,並列出每個預期內的字元串(’ABCD-EF’、’ABCDE-F’ 等),及其實際獲取的字元串(所有的都是’ABCD-EF’)。一旦確定doctest是充分的和正確的,就可以編寫該函數的主體部分,在本例中只是簡單的return string[:position] + insert+string[position:]。(如果我們編寫的是 return string[:position] + insert,之後複製 string [:position]並將其粘貼在末尾以便減少一些輸入操作,那麼doctest會立即提示錯誤。)

Python的標準庫提供了兩個單元測試模塊,一個是doctest,這裡和前面都簡單地提到過,另一個是unittest。此外,還有一些可用於Python的第三方測試工具。其中最著名的兩個是nose (code.google.com/p/python-nose)與py.test (codespeak.net/py/dist/test/test.html), nose 致力於提供比標準的unittest 模塊更廣泛的功能,同時保持與該模塊的兼容性,py.test則採用了與unittest有些不同的方法,試圖儘可能消除樣板測試代碼。這兩個第三方模塊都支持測試發現,因此沒必要寫一個總體的測試程序——因為模塊將自己搜索測試程序。這使得測試整個代碼樹或某一部分 (比如那些已經起作用的模塊)變得很容易。那些對測試嚴重關切的人,在決定使用哪個測試工具之前,對這兩個(以及任何其他有吸引力的)第三方模塊進行研究都是值 得的。

創建doctest是直截了當的:我們在模塊中編寫測試、函數、類與方法的docstrings。 對於模塊,我們簡單地在末尾添加了 3行:

if __name__ ==”__main__”:

import doctest

doctest.testmod()

在程序內部使用doctest也是可能的。比如,blocks.py程序(其模塊在後面)有自己函數的doctest,但以如下代碼結尾:

if __name__== “__main__”:

main()

這裡簡單地調用了程序的main()函數,並且沒有執行程序的doctest。要實驗程序的 doctest,有兩種方法。一種是導入doctest模塊,之後運行程序—比如,在控制台中輸 入 python3 -m doctest blocks.py (在 Wndows 平台上,使用類似於 C:Python3 lpython.exe 這樣的形式替代python3)。如果所有測試運行良好,就沒有輸出,因此,我們可能寧願執行python3-m doctest blocks.py-v,因為這會列出每個執行的doctest,並在最後給出結果摘要。

另一種執行doctest的方法是使用unittest模塊創建單獨的測試程序。在概念上, unittest模塊是根據Java的JUnit單元測試庫進行建模的,並用於創建包含測試用例的測試套件。unittest模塊可以基於doctests創建測試用例,而不需要知道程序或模塊包含的任何事物——只要知道其包含doctest即可。因此,為給blocks.py程序製作一個測試套件,我們可以創建如下的簡單程序(將其稱為test_blocks.py):

import doctest

import unittest

import blocks

suite = unittest.TestSuite()

suite.addTest(doctest.DocTestSuite(blocks))

runner = unittest.TextTestRunner()

print(runner.run(suite))

注意,如果釆用這種方法,程序的名稱上會有一個隱含的約束:程序名必須是有效的模塊名。因此,名為convert-incidents.py的程序的測試不能寫成這樣。因為import convert-incidents不是有效的,在Python標識符中,連接符是無效的(避開這一約束是可能的,但最簡單的解決方案是使用總是有效模塊名的程序文件名,比如,使用下劃線替換連接符)。這裡展示的結構(創建一個測試套件,添加一個或多個測試用例或測試套件,運行總體的測試套件,輸出結果)是典型的機遇unittest的測試。運行時,這一特定實例產生如下結果:

……………………………………………………………………………………………….

Ran 3 tests in 0.244s

OK

每次執行一個測試用例時,都會輸出一個句點(因此上面的輸出最前面有3個句點),之後是一行連接符,再之後是測試摘要(如果有任何一個測試失敗,就會有更多的輸出信息)。

如果我們嘗試將測試分離開(典型情況下是要測試的每個程序和模塊都有一個測試用例),就不要再使用doctests,而是直接使用unittest模塊的功能——尤其是我們習慣於使用JUnit方法進行測試時ounittest模塊會將測試分離於代碼——對大型項目(測試編寫人員與開發人員可能不一致)而言,這種方法特別有用。此外,unittest單元測試編寫為獨立的Python模塊,因此,不會像在docstring內部編寫測試用例時受到兼容性和明智性的限制。

unittest模塊定義了 4個關鍵概念。測試夾具是一個用於描述創建測試(以及用完之後將其清理)所必需的代碼的術語,典型實例是創建測試所用的一個輸入文件,最後刪除輸入文件與結果輸出文件。測試套件是一組測試用例的組合。測試用例是測試的基本單元—我們很快就會看到實例。測試運行者是執行一個或多個測試套件的對象。

典型情況下,測試套件是通過創建unittest.TestCase的子類實現的,其中每個名稱 以「test」開頭的方法都是一個測試用例。如果我們需要完成任何創建操作,就可以在一個名為setUp()的方法中實現;類似地,對任何清理操作,也可以實現一個名為 tearDown()的方法。在測試內部,有大量可供我們使用的unittest.TestCase方法,包括 assertTrue()、assertEqual()、assertAlmostEqual()(對於測試浮點數很有用)、assertRaises() 以及更多,還包括很多對應的逆方法,比如assertFalse()、assertNotEqual()、failIfEqual()、 failUnlessEqual ()等。

unittest模塊進行了很好的歸檔,並且提供了大量功能,但在這裡我們只是通過一 個非常簡單的測試套件來感受一下該模塊的使用。這裡將要使用的實例,該練習要求創建一個Atomic模塊,該模塊可以用作一 個上下文管理器,以確保或者所有改變都應用於某個列表、集合或字典,或者所有改變都不應用。作為解決方案提供的Atomic.py模塊使用30行代碼來實現Atomic類, 並提供了 100行左右的模塊doctest。這裡,我們將創建test_Atomic.py模塊,並使用 unittest測試替換doctest,以便可以刪除doctest。

在編寫測試模塊之前,我們需要思考都需要哪些測試。我們需要測試3種不同的數據類型:列表、集合與字典。對於列表,需要測試的是插入項、刪除項或修改項的值。對於集合,我們必須測試向其中添加或刪除一個項。對於字典,我們必須測試的是插入一個項、修改一個項的值、刪除一個項。此外,還必須要測試的是在失敗的情況下,不會有任何改變實際生效。

結構上看,測試不同數據類型實質上是一樣的,因此,我們將只為測試列表編寫測試用例,而將其他的留作練習。test_Atomic.py模塊必須導入unittest模塊與要進行測試的Atomic模塊。

創建unittest文件時,我們通常創建的是模塊而非程序。在每個模塊內部,我們定義一個或多個unittest.TestCase子類。比如,test_Atomic.py模塊中僅一個單獨的 unittest-TestCase子類,也就是TestAtomic (稍後將對其進行講解),並以如下兩行結束:

if name == “__main__”:

unittest.main()

這兩行使得該模塊可以單獨運行。當然,該模塊也可以被導入並從其他測試程序中運行——如果這只是多個測試套件中的一個,這一點是有意義的。

如果想要從其他測試程序中運行test_Atomic.py模塊,那麼可以編寫一個與此類似的程序。我們習慣於使用unittest模塊執行doctests,比如:

import unittest

import test_Atomic

suite = unittest.TestLoader().loadTestsFromTestCase(test_Atomic.TestAtomic)

runner = unittest.TextTestRunner()

pnnt(runner.run(suite))

這裡,我們已經創建了一個單獨的套件,這是通過讓unittest模塊讀取test_Atomic 模塊實現的,並且使用其每一個test*()方法(本實例中是test_list_success()、test_list_fail(),稍後很快就會看到)作為測試用例。

我們現在將查看TestAtomic類的實現。對通常的子類(不包括unittest.TestCase 子類),不怎麼常見的是,沒有必要實現初始化程序。在這一案例中,我們將需要建立 一個方法,但不需要清理方法,並且我們將實現兩個測試用例。

def setUp(self):

self.original_list = list(range(10))

我們已經使用了 unittest.TestCase.setUp()方法來創建單獨的測試數據片段。

def test_list_succeed(self):

items = self.original_list[:]

with Atomic.Atomic(items) as atomic:

atomic.append(1999)

atomic.insert(2, -915)

del atomic[5]

atomic[4]= -782

atomic.insert(0, -9)

self.assertEqual(items,

[-9, 0, 1, -915, 2, -782, 5, 6, 7, 8, 9, 1999])

def test_list_fail(self):

items = self.original_list[:]

with self.assertRaises(AttributeError):

with Atomic.Atomic(items) as atomic:

atomic.append(1999)

atomic.insert(2, -915)

del atomic[5]

atomic[4] = -782

atomic.poop() # Typo

self.assertListEqual(items, self.original_list)

這裡,我們直接在測試方法中編寫了測試代碼,而不需要一個內部函數,也不再使用unittest.TestCase.assertRaised()作為上下文管理器(期望代碼產生AttributeError)。 最後我們也使用了 Python 3.1 的 unittest.TestCase.assertListEqual()方法。

正如我們已經看到的,Python的測試模塊易於使用,並且極為有用,在我們使用 TDD的情況下更是如此。它們還有比這裡展示的要多得多的大量功能與特徵——比如,跳過測試的能力,這有助於理解平台差別——並且這些都有很好的文檔支持。缺失的一個功能——但nose與py.test提供了——是測試發現,儘管這一特徵被期望在後續的Python版本(或許與Python 3.2—起)中出現。

性能剖析(Profiling)

如果程序運行很慢,或者消耗了比預期內要多得多的內存,那麼問題通常是選擇的演算法或數據結構不合適,或者是以低效的方式進行實現。不管問題的原因是什麼, 最好的方法都是準確地找到問題發生的地方,而不只是檢査代碼並試圖對其進行優化。 隨機優化會導致引入bug,或者對程序中本來對程序整體性能並沒有實際影響的部分進行提速,而這並非解釋器耗費大部分時間的地方。

在深入討論profiling之前,注意一些易於學習和使用的Python程序設計習慣是有意義的,並且對提高程序性能不無裨益。這些技術都不是特定於某個Python版本的, 而是合理的Python程序設計風格。第一,在需要只讀序列時,最好使用元組而非列表; 第二,使用生成器,而不是創建大的元組和列表並在其上進行迭代處理;第三,盡量使用Python內置的數據結構 dicts、lists、tuples 而不實現自己的自定義結構,因為內置的數據結構都是經過了高度優化的;第四,從小字元串中產生大字元串時, 不要對小字元串進行連接,而是在列表中累積,最後將字元串列表結合成為一個單獨的字元串;第五,也是最後一點,如果某個對象(包括函數或方法)需要多次使用屬性進行訪問(比如訪問模塊中的某個函數),或從某個數據結構中進行訪問,那麼較好的做法是創建並使用一個局部變數來訪問該對象,以便提供更快的訪問速度。

Python標準庫提供了兩個特別有用的模塊,可以輔助調査代碼的性能問題。一個是timeit模塊——該模塊可用於對一小段Python代碼進行計時,並可用於諸如對兩個或多個特定函數或方法的性能進行比較等場合。另一個是cProfile模塊,可用於profile 程序的性能——該模塊對調用計數與次數進行了詳細分解,以便發現性能瓶頸所在。

為了解timeit模塊,我們將查看一些小實例。假定有3個函數function_a()、 function_b()、function_c(), 3個函數執行同樣的計算,但分別使用不同的演算法。如果將這些函數放於同一個模塊中(或分別導入),就可以使用timeit模塊對其進行運行和比較。下面給出的是模塊最後使用的代碼:

if __name__ == “__main__”:

repeats = 1000

for function in (“function_a”, “function_b”, “function_c”):

t = timeit.Timer(“{0}(X, Y)”.format(function),”from __main__ import {0}, X, Y”.format(function))

sec = t.timeit(repeats) / repeats

print(“{function}() {sec:.6f} sec”.format(**locals()))

賦予timeit.Timer()構造子的第一個參數是我們想要執行並計時的代碼,其形式是字元串。這裡,該字元串是「function_a(X,Y)」;第二個參數是可選的,還是一個待執行的字元串,這一次是在待計時的代碼之前,以便提供一些建立工作。這裡,我們從 __main__ (即this)模塊導入了待測試的函數,還有兩個作為輸入數據傳入的變數(X 與Y),這兩個變數在該模塊中是作為全局變數提供的。我們也可以很輕易地像從其他模塊中導入數據一樣來進行導入操作。

調用timeit.Timer對象的timeit()方法時,首先將執行構造子的第二個參數(如果有), 之後執行構造子的第一個參數並對其執行時間進行計時。timeit.Timer.timeit()方法的返回值是以秒計數的時間,類型是float。默認情況下,timeit()方法重複100萬次,並返回所 有這些執行的總秒數,但在這一特定案例中,只需要1000次反覆就可以給出有用的結果, 因此對重複計數次數進行了顯式指定。在對每個函數進行計時後,使用重複次數對總數進行除法操作,就得到了平均執行時間,並在控制台中列印出函數名與執行時間。

function_a() 0.001618 sec

function_b() 0.012786 sec

function_c() 0.003248 sec

在這一實例中,function_a()顯然是最快的——至少對於這裡使用的輸入數據而言。 在有些情況下一一比如輸入數據不同會對性能產生巨大影響——可能需要使用多組輸入數據對每個函數進行測試,以便覆蓋有代表性的測試用例,並對總執行時間或平均執行時間進行比較。

有時監控自己的代碼進行計時並不是很方便,因此timeit模塊提供了一種在命令行中對代碼執行時間進行計時的途徑。比如,要對MyModule.py模塊中的函數function_a()進行計時,可以在控制台中輸入如下命令:python3 -m timeit -n 1000 -s “from MyModule import function_a, X, Y” “function_a(X, Y)”(與通常所做的一樣,對 Windows 環境,我們必須使用類似於C:Python3lpython.exe這樣的內容來替換python3)。-m選項用於Python 解釋器,使其可以載入指定的模塊(這裡是timeit),其他選項則由timeit模塊進行處理。 -n選項指定了循環計數次數,-s選項指定了要建立,最後一個參數是要執行和計時的代碼。命令完成後,會向控制台中列印運行結果,比如:

1000 loops, best of 3: 1.41 msec per loop

之後我們可以輕易地對其他兩個函數進行計時,以便對其進行整體的比較。

cProfile模塊(或者profile模塊,這裡統稱為cProfile模塊)也可以用於比較函數 與方法的性能。與只是提供原始計時的timeit模塊不同的是,cProfile模塊精確地展示 了有什麼被調用以及每個調用耗費了多少時間。下面是用於比較與前面一樣的3個函數的代碼:

if __name__ == “__main__”:

for function in (“function_a”, “function_b”, “function_c”):

cProfile.run(“for i in ranged 1000): {0}(X, Y)”.format(function))

我們必須將重複的次數放置在要傳遞給cProfile.run()函數的代碼內部,但不需要做任何創建,因為模塊函數會使用內省來尋找需要使用的函數與變數。這裡沒有使用顯式的print()語句,因為默認情況下,cProfile.run()函數會在控制台中列印其輸出。下面給出的是所有函數的相關結果(有些無關行被省略,格式也進行了稍許調整,以便與頁面適應):

1003 function calls in 1.661 CPU seconds

ncalls tottime percall cumtime percall filename:lineno(function)

1 0.003 0.003 1.661 1.661 :1 ( )

1000 1.658 0.002 1.658 0.002 MyModule.py:21 (function_a)

1 0.000 0.000 1.661 1.661 {built-in method exec}

5132003 function calls in 22.700 CPU seconds

ncalls tottime percall cumtime percall filename:lineno(function)

1 0.487 0.487 22.700 22.700 : 1 ( )

1000 0.011 0.000 22.213 0.022 MyModule.py:28(function_b)

5128000 7.048 0.000 7.048 0.000 MyModule.py:29( )

1000 0.00 50.000 0.005 0.000 {built-in method bisectjeft}

1 0.000 0.000 22.700 22.700 {built-in method exec}

1000 0.001 0.000 0.001 0.000 {built-in method len}

1000 15.149 0.015 22.196 0.022 {built-in method sorted}

5129003 function calls in 12.987 CPU seconds

ncalls tottime percall cumtime percall filename:lineno(function)

1 0.205 0.205 12.987 12.987 :l ( )

1000 6.472 0.006 12.782 0.013 MyModule.py:36(function_c)

5128000 6.311 0.000 6.311 0.000 MyModule.py:37( )

1 0.000 0.000 12.987 12.987 {built-in method exec}

ncalls (“調用的次數”)列列出了對指定函數(在filename:lineno(function)中列出) 的調用次數。回想一下我們重複了 1000次調用,因此必須將這個次數記住。tottime (「總的時間」)列列出了某個函數中耗費的總時間,但是排除了函數調用的其他函數內部花費的時間。第一個percall列列出了對函數的每次調用的平均時間(tottime // ncalls)。 cumtime (“累積時間”)列出了在函數中耗費的時間,並且包含了函數調用的其他函數內部花費的時間。第二個percall列列出了對函數的每次調用的平均時間,包括其調用的函數耗費的時間。

這種輸出信息要比timeit模塊的原始計時信息富有啟發意義的多。我們立即可以發現,function_b()與function_c()使用了被調用5000次以上的生成器,使得它們的速度至少要比function_a()慢10倍以上。並且,function_b()調用了更多通常意義上的函數,包括調用內置的sorted()函數,這使得其幾乎比function_c()還要慢兩倍。當然,timeit() 模塊提供了足夠的信息來查看計時上存在的這些差別,但cProfile模塊允許我們了解為什麼會存在這些差別。正如timeit模塊允許對代碼進行計時而又不需要對其監控一樣,cProfile模塊也可以做到這一點。然而,從命令行使用cProfile模塊時,我們不能精確地指定要執行的 是什麼——而只是執行給定的程序或模塊,並報告所有這些的計時結果。需要使用的 命令行是python3 -m cProfile programOrModule.py,產生的輸出信息與前面看到的一 樣,下面給出的是輸出信息樣例,格式上進行了一些調整,並忽略了大多數行:

10272458 function calls (10272457 primitive calls) in 37.718 CPU secs

ncalls tottime percall cumtime percall filename:lineno(function)

10.000 0.000 37.718 37.718 :1 ( )

10.719 0.719 37.717 37.717 :12( )

1000 1.569 0.002 1.569 0.002 :20(function_a)

1000 0.011 0.000 22.560 0.023 :27(function_b)

5128000 7.078 0.000 7.078 0.000 :28( )

1000 6.510 0.007 12.825 0.013 :35(function_c)

5128000 6.316 0.000 6.316 0.000 :36( )

在cProfile術語學中,原始調用指的就是非遞歸的函數調用。

以這種方式使用cProfile模塊對於識別值得進一步研究的區域是有用的。比如,這裡 我們可以清晰地看到function_b()需要耗費更長的時間,但是我們怎樣獲取進一步的詳細資料?我們可以使用cProfile.run(“function_b()”)來替換對function_b()的調用。或者可以保存完全的profile數據並使用pstats模塊對其進行分析。要保存profile,就必須對命令行進行稍許修改:python3 -m cProfile -o profileDataFile programOrModule.py。 之後可以對 profile 數據進行分析,比如啟動IDLE,導入pstats模塊,賦予其已保存的profileDataFile,或者也可以在控制台中互動式地使用pstats。

下面給出的是一個非常短的控制台會話實例,為使其適合頁面展示,進行了適當調整,我們自己的輸入則以粗體展示:

$ python3 -m cProfile -o profile.dat MyModule.py

$ python3 -m pstats

Welcome to the profile statistics browser.

% read profile.dat

profile.dat% callers function_b

Random listing order was used

List reduced from 44 to 1 due to restriction

Function was called by…

ncalls tottime cumtime

:27(function_b) – 1000 0.011 22.251 :12( )

profile.dat% callees function_b

Random listing order was used

List reduced from 44 to 1 due to restriction

Function called…

ncalls tottime cumtime

:27(function_b)-

1000 0.005 0.005 built-in method bisectJeft

1000 0.001 0.001 built-in method len

1000 1 5.297 22.234 built-in method sorted

profile.dat% quit

輸入help可以獲取命令列表,help後面跟隨命令名可以獲取該命令的更多信息。比如, help stats將列出可以賦予stats命令的參數。還有其他一些可用的工具,可以提供profile數據的圖形化展示形式,比如 RunSnakeRun (), 該工具需要依賴於wxPython GUI庫。

使用timeit與cProfile模塊,我們可以識別出我們自己代碼中哪些區域會耗費超過預期的時間;使用cProfile模塊,還可以準確算岀時間消耗在哪裡。

以上內容部分摘自視頻課程 05後端編程Python-19調試、測試和性能調優(下) ,更多實操示例請參照視頻講解。跟著張員外講編程,學習更輕鬆,不花錢還能學習真本領。

原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/198599.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
小藍的頭像小藍
上一篇 2024-12-04 10:26
下一篇 2024-12-04 10:26

相關推薦

  • Python計算陽曆日期對應周幾

    本文介紹如何通過Python計算任意陽曆日期對應周幾。 一、獲取日期 獲取日期可以通過Python內置的模塊datetime實現,示例代碼如下: from datetime imp…

    編程 2025-04-29
  • Python周杰倫代碼用法介紹

    本文將從多個方面對Python周杰倫代碼進行詳細的闡述。 一、代碼介紹 from urllib.request import urlopen from bs4 import Bea…

    編程 2025-04-29
  • Python中引入上一級目錄中函數

    Python中經常需要調用其他文件夾中的模塊或函數,其中一個常見的操作是引入上一級目錄中的函數。在此,我們將從多個角度詳細解釋如何在Python中引入上一級目錄的函數。 一、加入環…

    編程 2025-04-29
  • Python列表中負數的個數

    Python列表是一個有序的集合,可以存儲多個不同類型的元素。而負數是指小於0的整數。在Python列表中,我們想要找到負數的個數,可以通過以下幾個方面進行實現。 一、使用循環遍歷…

    編程 2025-04-29
  • 如何查看Anaconda中Python路徑

    對Anaconda中Python路徑即conda環境的查看進行詳細的闡述。 一、使用命令行查看 1、在Windows系統中,可以使用命令提示符(cmd)或者Anaconda Pro…

    編程 2025-04-29
  • python強行終止程序快捷鍵

    本文將從多個方面對python強行終止程序快捷鍵進行詳細闡述,並提供相應代碼示例。 一、Ctrl+C快捷鍵 Ctrl+C快捷鍵是在終端中經常用來強行終止運行的程序。當你在終端中運行…

    編程 2025-04-29
  • Python字典去重複工具

    使用Python語言編寫字典去重複工具,可幫助用戶快速去重複。 一、字典去重複工具的需求 在使用Python編寫程序時,我們經常需要處理數據文件,其中包含了大量的重複數據。為了方便…

    編程 2025-04-29
  • Python清華鏡像下載

    Python清華鏡像是一個高質量的Python開發資源鏡像站,提供了Python及其相關的開發工具、框架和文檔的下載服務。本文將從以下幾個方面對Python清華鏡像下載進行詳細的闡…

    編程 2025-04-29
  • Python程序需要編譯才能執行

    Python 被廣泛應用於數據分析、人工智慧、科學計算等領域,它的靈活性和簡單易學的性質使得越來越多的人喜歡使用 Python 進行編程。然而,在 Python 中程序執行的方式不…

    編程 2025-04-29
  • 蝴蝶優化演算法Python版

    蝴蝶優化演算法是一種基於仿生學的優化演算法,模仿自然界中的蝴蝶進行搜索。它可以應用於多個領域的優化問題,包括數學優化、工程問題、機器學習等。本文將從多個方面對蝴蝶優化演算法Python版…

    編程 2025-04-29

發表回復

登錄後才能評論