python 調試方法總結

說在前面

我覺得沒有什麼錯誤是調試器無法解決的,如果沒有,那我再說一遍,如果有,那當我沒說

一、拋出異常

可以通過 raise 語句拋出異常,使程序在我們已經知道的缺陷處停下,並進入到 except 語句
 

raise句法:

raise關鍵字

調用的異常函數名 ValueError (這個函數必須是異常類或一個實例)

傳遞給 ValueError 的字符串,包含有用的出錯信息

>>> raise ValueError('This is a error message')
Traceback (most recent call last):
  File "<pyshell#24>", line 1, in <module>
    raise ValueError('This is a error message')
ValueError: This is a error message

然後使用 try...except 語句來對拋出的異常做處理

通常我們在函數本身中拋出異常,然後在調用該函數的地方使用 try...except 語句處理異常

#定義一個簡單的int類型的加法器
def calculator(num1,num2):
    if isintance(num1,int)and isintance(num2,int):
        raise Exception('Symbol must be a int type number.')
    return num1+num2
 
print('please enter two number:')
num1=input()
num2=input()
#在調用函數的地方使用try語句
try:
    print(calculator(num1,num2))
except Exception as err:
    print('發生了一個錯誤:'+str(err))
#另一種使用情況
try:
            print(key)
            return self[key]
        except KeyError:#如果在上面遇見了keyError
            raise AttributeError(r"'%s' don't have attribute '%s'"%#就拋出這個AttributeError類型的錯誤,順序別弄錯<br>(self.__class__.name,key))

注意上面的 as 語句取得 str ,如果不取也是可以的
  
運行示例:

RESTART: C:/Users/Administrator.SC-201605202132/AppData/Local/Programs/Python/Python37/boxPrint.py 
please enter two number:
s
發生了一個錯誤:name 'isintance' is not defined
>>>

二、取的反向跟蹤的字符串

當程序運行出現錯誤時,python會生成一些錯誤信息,這些錯誤信息被稱爲“反向跟蹤”,它包含了出錯信息、導致該錯誤的代碼行號,和導致 該錯誤的函數調用 的 序列,這個序列被稱爲調用棧。

只要拋出的異常沒有被處理,python就會顯示反向跟蹤

以下面程序來展示我們對反向跟蹤的解讀

def spam():
    bacon()
def bacon():
    raise Exception('This is the error message')
 
spam()

這就是反向跟蹤:

Traceback (most recent call last):
  File "C:/Users/Administrator.SC-201605202132/AppData/Local/Programs/Python/Python37/errorExample.py", line 6, in <module>
    spam()
  File "C:/Users/Administrator.SC-201605202132/AppData/Local/Programs/Python/Python37/errorExample.py", line 2, in spam
    bacon()
  File "C:/Users/Administrator.SC-201605202132/AppData/Local/Programs/Python/Python37/errorExample.py", line 4, in bacon
    raise Exception('This is the error message')
Exception: This is the error message

我們應該從下往上閱讀方向跟蹤,通過反向跟蹤我們可以知道,這個錯誤發生在第5行,在bacon函數中;這次特定的bacon調用發生在第2行,spam函數中,而spam函數又是在第6行被調用的。這樣,在從多個位置調用函數的程序中,調用棧就能幫助你確定那次調用導致了錯誤。

調用 traceback.format_exc() 得到反向跟蹤的字符串形式

前面說過,如果拋出的異常沒有被處理,python纔會顯示反向跟蹤。假如我們既想用except處理錯誤,又想要獲得出錯信息,就可以用這個函數,需要導入 traceback 模塊

例如,我們可以在程序出現錯誤時還能繼續運行,同時把錯誤信息記錄到日誌中。在程序結束後調試程序時,我們就根據日誌裏記錄的信息去調試

>>> import traceback
>>> try:
    raise Exception('This is a error message')
except:
    errorFile=open('errorInfo.txt','w')
    errorFile.write(traceback.format_exc())  #使用tracback.format_exc()獲得反向跟蹤的字符串形式
    errorFile.close()
    print('The traceback info was written to errorInfo.txt')
 
     
112    #返回的是寫入的字符個數
The traceback info was written to errorInfo.txt
>>>

errorInfo.txt的內容:

Traceback (most recent call last):
  File "<pyshell#8>", line 2, in <module>
Exception: This is a error message

三、斷言assert語句

舉一個例子。你從學校畢業以後,很久都沒有找到工作,有一天你找了一個兼職:寶石大管家。小孩需要拿着與他們身份匹配的標識才能在別處領到寶石,這個標識在你這裏領取,你工作做得不錯,才做了五分鐘就被老闆任命爲了區域經理,你覺你年紀輕輕就已經成爲了二龍山雲霄飛車街區的揸Fit人、並且一手建立了二龍山遊樂場寶石交易的遊戲法則,覺得人生巔峯也不過如此,但是,沉迷於自我陶醉的你根本不知道,你將一個錯誤的標識給了一個小朋友,導致他沒有領到寶石。結果他叫他哥哥來打你了一頓。然後你老闆覺得你辜負了他對你的栽培,然後一氣之下把你開了,工資當然沒有結。最慘的是,你的衣服丟了,當時你爲了用肚臍眼上的傷疤嚇唬他就把衣服脫了,結果他竟然也有同樣的傷疤,然後又被他打了一頓,然後,你的衣服就丟了。你知道這是你最寶貴的財富,因爲這是當年女神贈你的禮物,你永遠也忘不了畢業那天,在你的寢室樓下,他輕輕的把袋子遞給了你,那天你們說了很多,他說感謝你四年來對他的照顧,但是他媽媽不讓他談戀愛,所以讓你再等等,你和他一直聊到晚上10點,只爲了能當面向他說一句晚安,他很欣賞你的執着,離別之際對你許下了一個承諾:她說假如有一天這件衣服變成了綠色,他一定和你結婚。你知道,這下肯定沒有希望了。不僅失去了工作,你失去了愛情。你以爲丟了衣服,就再也沒機會和他結婚了,萬萬沒想到,最後你們還是成爲了夫妻。那天你回來以後就去了網吧,看見旁邊的人在寫代碼,他周圍散落的零食包裝代表着富有,這一切都被你看在眼裏,你知道你看到了希望,然後你就開始學編程了,由於你過人的天賦,沒出幾十年你就自己創辦了一家公司,和阿里啪啪,中國移不動等大公司都建立了不同程度的合作關係,且業務往來十分密切,身邊的人都誇你有出息,只是在深夜的時候,你常常想起當年的那個他,你祈求老天再給你一次機會,終於有一天,你qq收到了他的信息,她說要來找你,你在城市最有檔次的地方約她吃飯,他一眼就認出來了你,你很開心,你覺得他一點都沒變,還是原來的樣子,他沒有問你衣服的事情,只是不停的向你道歉說是手誤當時才把你刪了,其實他這些年一直在找你,這次找到你了,就是要和你結婚,你十分激動,但是你強忍着激動的心情,勸他在考慮考慮,他搖了搖頭,從他眼神裏流露出來的堅定瞬間擊垮了你,你再也控制不了自己了,你拿出了那次做兼職留下的寶石鑽戒,你一直把它帶在身上,就是等着機會到來,他想都沒想就一口答應了你的求婚。看到他對你如此信賴,你暗暗發誓一定要用全部的智商去愛她,晚上他非要枕着你的胳膊睡覺,你雖然覺的不舒服但還是讓他枕了一夜,你做了一個夢,夢見你們有了自己的孩子,那件衣服也被你找到了
衣服上還寫着“前方高能”幾個字,這是你睡得最舒服的一個晚上,你早早就醒來了,發現他也已經起來了,就在牀邊上坐着,但令你不解的是,看到你睜開了眼睛,他的表情忽然很激動,sua的一聲就哭了,等他冷靜下來你才知道。原來,你應經昏迷了8年了,8年前,你去買早餐就再也沒有回來,你出了車禍,昏迷了8年,留下他和他腹中的孩子。他說這些年他從來沒有想過放棄你,他對你的愛幫助他克服了許多困難。如今你醒了,他終於成功了,他高興的留下了激動的淚水,你也很開心。於是從此以後,你們一家三口過上了幸福的生活。

 

“斷言”在這個工作流程當中,就是用來檢查 你是否把牌發對了 的一個機制。爲了避免這樣的情況,我們就添加“斷言”來檢查。

assert語句包含:

assert關鍵字、要判斷的條件、逗號、條件爲False時顯示的字符串

>>> podBayDoorStatus='open'  #吊艙門的狀態
>>> assert podBayDoorStatus=='open','podBayDoorStatus需要設置爲open'
#這裏結果沒有錯
>>> podBayDoorStatus='other content'
>>> assert podBayDoorStatus=='open','podBayDoorStatus需要設置爲open'
#這裏結果出錯了
Traceback (most recent call last):
  File "<pyshell#13>", line 1, in <module>
    assert podBayDoorStatus=='open','podBayDoorStatus需要設置爲open'
AssertionError: podBayDoorStatus需要設置爲open
>>>

我們在程序中爲某個變量賦值後,基於 這個變量是這個值 的假定,我們可能寫下了大量的代碼,即這些代碼依賴這個值,才能正確工作。說以我們添加一個斷言,確保假定的變量值是對的。

對於這種情況,我們使用assert讓程序立即崩潰就,以減少尋找缺陷的時間,我們不應用 try except 拋出異常,因爲這是程序員的錯誤,而不是用戶的錯誤,對於那些可以恢復的錯誤(如文件沒有找到,用戶輸入了無效的數據)則應該用拋出異常來處理

  

在交通燈模擬中使用斷言

  

你在編寫一個交通信號燈的模擬程序。代表路口信號燈的數據結構是一個字典:

market_2nd={'ns':'green','ew':'red'}#ns南北向,ew東西向

你希望編寫一個函數 switchLight() ,他接受一個路口字典作爲參數,並切換紅路燈

你可能認爲 switchLight() 只要將每一種燈按順序切換到下一種顏色: ‘green‘ 值應該切換到 'yellow' , 'yellow' 應該切換到 'red' , 'red' 應該切換到 'green' 實現這個功能的代碼:

def switchLights(stoplight):
    for key in stoplight.keys():
        if stoplight[key]=='green':
            stoplight[key]='yellow'
        elif stoplight[key]=='yellow':
            stoplight[key]='red'
        elif stoplight[key]=='red':
            stoplight[key]='green'

這樣的運行結果:

>>>
 RESTART: C:\Users\Administrator.SC-201605202132\AppData\Local\Programs\Python\Python37\forTest.py
{'ns': 'yellow', 'ew': 'green'}
{'ns': 'red', 'ew': 'yellow'}
{'ns': 'green', 'ew': 'red'}

你應該發現第一次的輸出是錯誤的,因爲南北向和東西向總應該有一個是紅色的,如果不是,那麼就會出現汽車相撞,爲了避免這樣的缺陷出現,你應該添加斷言

market_2nd={'ns':'green','ew':'red'}#ns南北向,ew東西向
 
def switchLights(stoplight):
    for key in stoplight.keys():
        if stoplight[key]=='green':
            stoplight[key]='yellow'
        elif stoplight[key]=='yellow':
            stoplight[key]='red'
        elif stoplight[key]=='red':
            stoplight[key]='green'
    assert 'red' in stoplight.values(),'交通燈都不是紅色的'+str(stoplight)        #在函數裏面添加斷言
switchLights(market_2nd)
print(market_2nd)
switchLights(market_2nd)
print(market_2nd)
switchLights(market_2nd)
print(market_2nd)

假如你沒有看出來這個代碼有問題,然後也沒有使用斷言,當你從運行結果發現問題時,或許要好多時間才能發現問題出現在 stwitchLight 函數中

禁用斷言

當我們開發測試的時候,我們可以使用斷言來幫助我們更早的發現錯誤,但是程序交付的時候應該是沒有缺陷的,這時就不在需要斷言了,我們可以在運行python時傳入-O選項來禁用斷言

需要從終端窗口運行程序時使用 >>>從終端運行程序<<<

在這裏插入圖片描述

四、日誌

記日誌是一種很好的方式,讓我們可以理解程序中發生的事,以及事情發生的順序。python中的 logging 模塊讓你能很容易的創建自定義的消息記錄。這些日誌消息列出了你指定的 任何變量 當時的值。缺失日誌消息表明有一部分代碼被跳過了,從未執行

4.1使用日誌模塊

import logging
logging.basicConfig(level=logging.DEBUG,format=' %(asctime)s - %(levelname)s - %(message)s')

我們使用 logging.debug('string') 來打印日誌信息,這個 debug() 函數會調用 basicConfig ,所以我們第二行是指定打印信息的格式

python記錄一個時間的日誌時,他會創建一個 logRecord 對象,保存關於該事件的信息。

logging.debug() 調用不僅打印出了我們傳遞給他的信息,而且包含時間戳和一個單詞DEBUG

我們以下面的程序爲例,展示使用日誌來調試程序的大致過程

import logging
logging.basicConfig(level=logging.DEBUG,format=' %(asctime)s - %(levelname)s - %(message)s')
logging.debug('Start of program')
 
def factorial(n):
    logging.debug('Start of factorial(%s%%)' %(n))  #這裏的兩個%是什麼意思?或許是匹配basicConfig()裏format裏的後兩個參數?
    total=1
    for i in range(n+1):
        total*=i
        logging.debug('i is '+str(i)+', total is '+str(total))
    logging.debug('End of factorial(%s%%)'%(n))
    return total
 
print(factorial(5))
logging.debug('End of program')

運行結果:

RESTART: C:/Users/Administrator.SC-201605202132/AppData/Local/Programs/Python/Python37/facatorialLog.py
 2019-03-06 17:39:10,889 - DEBUG - Start of program
 2019-03-06 17:39:10,938 - DEBUG - Start of factorial(5%)
 2019-03-06 17:39:10,973 - DEBUG - i is 0, total is 0
 2019-03-06 17:39:11,001 - DEBUG - i is 1, total is 0
 2019-03-06 17:39:11,030 - DEBUG - i is 2, total is 0
 2019-03-06 17:39:11,058 - DEBUG - i is 3, total is 0
 2019-03-06 17:39:11,083 - DEBUG - i is 4, total is 0
 2019-03-06 17:39:11,108 - DEBUG - i is 5, total is 0
 2019-03-06 17:39:11,132 - DEBUG - End of factorial(5%)
0
 2019-03-06 17:39:11,187 - DEBUG - End of program

從裏面我們可以看到i是從0開始的,這就導致了total變量總是0,當然結果也是0,知道了這些,我們就可以對程序進行改動

import logging
logging.basicConfig(level=logging.DEBUG,format=' %(asctime)s - %(levelname)s - %(message)s')
logging.debug('Start of program')
 
def factorial(n):
    logging.debug('Start of factorial(%s%%)' %(n))
    #或許是匹配basicConfig()裏format裏的後兩個參數?
    total=1
    for i in range(1,n+1):    #改動在這裏
       
---snip--

PS:遇到問題沒人解答?需要Python學習資料?可以加點擊下方鏈接自行獲取
note.youdao.com/noteshare?id=2dce86d0c2588ae7c0a88bee34324d76

4.2日誌級別

這個級別是全局的

“日誌級別”提供了一種方式,按重要性把日誌消息分爲了下面5類。這些級別只是一種建議,在工作中,還是有我們自己來爲日誌消息指定類型。就像上面,我們也可以不用 logging.debug() 而選用其他四種

python中的日誌級別
|級別(上面的是最小的) |日誌函數 |描述|
|--|--|--|
DEBUG| logging.debug() |最低級別。用於小細節。通常你只有在診斷問題時才需要
INFO| logging.info() |用於記錄程序中一般事件的信息,或者是用來確認工作正常
WARNING| logging.warning() |用於表示可能的問題,這些問題不會阻止程序的工作,但將來可能會
ERROR| logging.error() |用於記錄錯誤,它導致程序做某事失敗
CRITICAL |logging.critical() |最高級別。用於表示致命的錯誤,它導致或將要導致程序完全停止工作
  
他們顯示的格式並區別

>>> import logging
>>> logging.basicConfig(level=logging.DEBUG,format=' %(asctime)s - %(levelname)s - %(message)s')
>>> logging.debug('Some debugging details')
 2019-03-06 18:13:44,829 - DEBUG - Some debugging details
>>> logging.info('The logging is working')
 2019-03-06 18:13:59,984 - INFO - The logging is working
>>> logging.critical('The program is unable to recover!')
 2019-03-06 18:14:34,237 - CRITICAL - The program is unable to recover!
>>>

“日誌級別”的好處

“日誌級別”的好處在於,你可以改變想看到的 日誌消息 的優先級。這通過 basicConfig() 函數的level關鍵字參數來指定, level='logging.DEBUG' 時會顯示所有的日誌級別消息, level='logging.ERROR' 時只會顯示級別大於等於ERROR的日誌消息

當我們開發了更多程序後,我們可能只會對錯誤感興趣,這種情況,就可以通過上面的level參數來設定我們想看到的級別

4.3禁用日誌

logging.disable() 函數接受一個日誌級別,它會禁止該級別和更低級別的所有日誌消息,注意這個參數的書寫正確

>>> import logging
>>> logging.basicConfig(level=logging.DEBUG,format=' %(asctime)s - %(levelname)s - %(message)s')
>>> logging.critical('The program is unable to recover!')
 2019-03-06 18:14:34,237 - CRITICAL - The program is unable to recover!
>>> logging.disable(logging.CRITICAL)
>>> logging.critical('The program is unable to recover!')#由於上面的禁用這個就不顯示了
>>>

  
我們應該吧 logging.disable() 寫在程序中接近 import logging 代碼行的位置

4.4將日誌記錄到文件

logging.basicConfig() 函數接受 filename 關鍵字參數,日誌消息將被保存到 myProgramLog.txt 文件中,而不會在輸出在屏幕上

>>> import logging
>>>logging.basicConfig(filename='myProgramlog.txt',level=logging.DEBUG,format=' %(asctime)s - %(levelname)s - %(message)s')

4.5 basicConfig 的參數及 logging 模塊定義的格式字符串字段

參數名稱 描述
filename 指定日誌輸出目標文件的文件名,指定該設置項後日志信心就不會被輸出到控制檯了
filemode 指定日誌文件的打開模式,默認爲'a'。需要注意的是,該選項要在filename指定時纔有效
format 指定日誌格式字符串,即指定日誌輸出時所包含的字段信息以及它們的順序。logging模塊定義的格式字段下面會列出。
datefmt 指定日期/時間格式。需要注意的是,該選項要在format中包含時間字段%(asctime)s時纔有效
level 指定日誌器的日誌級別
stream 指定日誌輸出目標stream,如sys.stdout、sys.stderr以及網絡stream。需要說明的是,stream和filename不能同時提供,否則會引發 ValueError異常
style Python 3.2中新添加的配置項。指定format格式字符串的風格,可取值爲'%'、'{'和'$',默認爲'%'
handlers Python 3.3中新添加的配置項。該選項如果被指定,它應該是一個創建了多個Handler的可迭代對象,這些handler將會被添加到root logger。需要說明的是:filename、stream和handlers這三個配置項只能有一個存在,不能同時出現2個或3個,否則會引發ValueError異常。

五、IDLE的調試器  

"調試器"是IDLE的一項功能,他可以讓你每次執行一行代碼,並讓你清除的查看當前時刻所有變量的值,對於你弄明白程序的問題很有幫助,通過在交互窗口中點擊 Debug>Debugger 來打開 調試控制窗口

5.1窗口上的信息

調試的時候不要把把斷點打到類似while語句上,因爲while這樣的語句只執行一次,執行多次的是裏面包裹的代碼,所以單步跳出或者繼續的時候就相當於繼續執行到這個while語句,結束了要想一次讓單步跳出或者繼續達到一次執行一輪 while裏面代碼的效果,就把斷點打到while裏面

打開調試窗口後,只要你運行程序調試器就會在第一條指令執行前暫停執行,並顯示下面的信息:

將要執行的代碼行;所有局部變量其其值得列表;所有全局變量及其值的列表

你會發現這裏面有多你沒有定義的變量,如 __ builtins__ 、 __ doc__ 、 __ file__ ,等等。它們是python在運行程序時,自動設置的變量。這些變量代表的含義我現在也不知道。我們可以只關注那些我們定義的變量。

程序將保持暫停,知道我們按下調試窗口的5個按鈕中的一個:GO、Step、Over、Out、Quit

Go

點擊Go按鈕將導致程序正常執行至終止,或到達一個“斷點”(斷點稍後會說)。換句話說,如果你完成了調試,希望程序正常繼續,就點擊Go按鈕

Step

Step按鈕將導致程序執行下一行代碼,然後再次暫停。如果下一行代碼是一個函數調用,調試器就會“步入”那個函數,調到該函數的第一行。

Over

Over按鈕將執行下一行代碼,與Step按鈕類似。但是如果下一行代碼是一個函數調用,Over按鈕將“跨越”該函數的代碼,調試器將在該函數返回後暫停。例如,下一行代碼是 print() 調用,而顯然我們不關注 print() 這個函數的代碼是怎樣的工作的,只希望傳遞給它的字符串打印出來,這時我們就可以使用Over按鈕

Out

Out按鈕將導致調試器全速執行代碼行,直到它從當前函數返回。如果你用Step按鈕進入了一個函數,現在想要讓這個函數全速執行,直到這個函數結束,那麼就可以使用Out按鈕,讓他從當前函數調用中“走出來”

Quit

Quit按鈕將馬上終止該程序,不會執行下面的代碼,記住是終止程序,不是終止調試

5.2關閉調試器

和打開的操作一樣,從交互式窗口點擊 Debug>Debugger 就會關閉

5.3斷點

“斷點”可以設置在特定的代碼行上,當使用調試器開始調試程序時,按下GO按鈕並不會結束程序了,而是會到達斷點裏暫停。

我們可以在編輯器裏在要設定斷點的行右擊鼠標,選擇 Set Breakpoint ,就在當前行設置了斷點,並且會以亮黃色顯示,這次我們打開調試器後,再運行程序後按GO按鈕就會在這一行停止,當我們要清除斷點時,需要在當前行右擊鼠標,選擇 clear Breakpoint

當我們想要知道for循環中某一輪中的變量值,我們就可以在那一行設置斷點,而不是頻繁的點擊Over按鈕

import random
mark=0
for i in range(1,1000):
    s=random.randint(0,2)
    if s==1:
        mark+=1
#我們查看循環到i=500時的mark值就可以在下面設置斷點   
    if i==500:
       print('halfway done')    #設置這裏爲斷點,而不要在上一行裏設置,因爲他是個判斷,每一輪都會運行
print(mark)

斷言、異常、日誌和調試器,都是在程序中發現錯誤和預防缺陷的有用工具。用python的斷言,是檢查自己有沒有犯錯的好方式。如果必要的條件被我們搞錯了,他將會早早的給出警告。斷言所針對的錯誤,是程序不應該嘗試恢復的,而是應該讓程序立馬失敗

異常可以由 try...except 語句捕捉和處理。 logging 模塊是一種很好的方式,可以在運行時查看代碼的內部,他比使用 pring() 語句要好很多,因爲他有不同的日誌級別,並能寫入日誌文件。

調試器讓你每次單步執行一行代碼。或者可以用正常的速度運行程序,並讓調試器停在你設置的斷點的代碼行上。利用調試器,你可以看到程序在運行期間,任何時候所有變量的值。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章