說在前面
我覺得沒有什么錯誤是調試器無法解決的,如果沒有,那我再說一遍,如果有,那當我沒說
一、拋出異常
可以通過 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類型的錯誤,順序別弄錯
(self.__class__.name,key))
注意上面的 as 語句取得 str ,如果不取也是可以的
運行示例:
RESTART: C:/Users/Administrator.SC-201605202132/AppData/Local/Programs/Python/Python37/boxPrint.py please enter two number: 1 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--
4.2日志級別
這個級別是全局的
“日志級別”提供了一種方式,按重要性把日志消息分為了下面5類。這些級別只是一種建議,在工作中,還是有我們自己來為日志消息指定類型。就像上面,我們也可以不用 logging.debug() 而選用其他四種
級別(上面的是最小的) | 日志函數 | 描述 |
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() 語句要好很多,因為他有不同的日志級別,並能寫入日志文件。
調試器讓你每次單步執行一行代碼。或者可以用正常的速度運行程序,並讓調試器停在你設置的斷點的代碼行上。利用調試器,你可以看到程序在運行期間,任何時候所有變量的值。