#7 Python代碼調試

前言

Python已經學了這麼久了,你現在已經長大了,該學會自己調試代碼了!相信大家在編寫程序過程中會遇到大量的錯誤信息,我也不例外的啦~遇到這些問題該怎麼解決呢?使用最多的方法就是使用print打印中間變量了哇,關於這種方法怎麼說呢~low!!!這一節將記錄Python中一項很重要的技能:Debug(代碼調試),Here We Go!

一、代碼調試概述

1.1 概述

一個程序員在編寫項目的時候,敲代碼其實並不會佔用太多的時間,佔用時間的其實是敲代碼之前(整個項目的思路和框架)和敲代碼之後(調試代碼)。調試代碼這個過程是最讓人煩心的事情了,真的是煩到脫髮~於是有一項過硬的Debug技巧將會減緩掉頭髮的速度。Debug的方法有很多,最常用的就是:打印中間變量(print)、使用日誌模塊(logging)、使用代碼調試模塊(pdb或ipdb)。接下來將會一一講解

二、Debug方法一:print函數

2.1 print方法適用情景

在程序報錯或者結果與預期不符合時,在源代碼中直接使用print函數打印中間變量進行檢查。

2.2 print方法例子

 1 '''
 2 從下列段落中提取出所有数字,並輸出
 3 本例結果應該是:49737
 4 '''
 5 import re
 6 
 7 
 8 test = ''' JAKARTA, Indonesia—Flag carrier Garuda Indonesia said it is seeking to cancel an order for 49 Boeing Co. 737 MAX jets, saying passengers have lost confidence in the aircraft following two deadly crashes in recent months.  '''
 9 
10 pattern = re.compile('\d')
11 
12 result = re.findall(pattern, test)
13 
14 print(result[0] + result[1])
15 
16 # 運行結果:
17 49

題目要求輸出49737,但是自己的程序卻只輸出了49,這是怎麼回事呢?

那就使用print打印一下result這個變量的內容哇,於是,在第13行代碼中加入 print(result) :

 1 '''
 2 從下列段落中提取出所有数字,並輸出
 3 本例結果應該是:49737
 4 '''
 5 import re
 6 
 7 
 8 test = ''' JAKARTA, Indonesia—Flag carrier Garuda Indonesia said it is seeking to cancel an order for 49 Boeing Co. 737 MAX jets, saying passengers have lost confidence in the aircraft following two deadly crashes in recent months.  '''
 9 
10 pattern = re.compile('\d')
11 
12 result = re.findall(pattern, test)
13 print(result)
14 
15 print(result[0] + result[1])
# 運行結果:
['4', '9', '7', '3', '7']
49

這時就會發現原來是result變量有誤,預期result效果為[’49’, ‘737’]

於是回過頭去檢查pattern,發現是pattern的鍋,應將pattern改為:

pattern = re.compile('\d+')

於是整個程序變為:

 3 '''
 4 從下列段落中提取出所有数字,並輸出
 5 本例結果應該是:49737
 6 '''
 7 import re
 8 
 9 
10 test = ''' JAKARTA, Indonesia—Flag carrier Garuda Indonesia said it is seeking to cancel an order for 49 Boeing Co. 737 MAX jets, saying passengers have lost confidence in the aircraft following two deadly crashes in recent months.  '''
11 
12 # pattern = re.compile('\d')
13 pattern = re.compile('\d+')
14 
15 result = re.findall(pattern, test)
16 print(result)
17 
18 print(result[0] + result[1])
# 運行結果:
['49', '737']
49737

結果與預期一樣,最後再將 print(result) 這一行刪除,整個程序就完工了。

『防抄襲:讀者請忽略這段文字,文章作者是博客園的MinuteSheep

2.3 print方法優缺點

優點:

  • 理解和操作方便簡單,上手難度低
  • 在編寫程序中可以一步一步檢查中間變量是否符合預期

缺點:

  • 直接入侵源代碼,如果操作失誤可能會造成不可挽救的後果
  • 調試完成后需要將這些print行刪除掉或者註釋掉,否則會造成整個程序運行結果複雜,同時太多print函數的出現會嚴重拖慢運行速度

三、Debug方法二:logging模塊

3.1 日誌概述

日誌是個什麼鬼呢?感覺好像日記的樣子哎~日誌其實和日記是有很大差別的,日誌是用來追蹤程序運行過程中發生的事情,將這些事情按照一定的格式寫入特定的文件中,以後可以通過分析日誌,讓管理者更加方便地了解整個程序的的運行情況,尤其是了解到程序的健康狀態(優秀的日誌分析者甚至可以通過日誌分析出開發者的操作習慣和興趣愛好),最後根據這些結果為程序打上合適的補丁。

3.2 日誌作用

  • 代碼調試
  • 記錄程序的運行狀況
  • 為程序打補丁提供支撐

3.3 日誌等級

在講Python日誌方法之前,先來了解一下日誌中最重要的等級制度:

通常日誌分為5個等級:DEBUG, INFO, WARNING, ERROR, CRITICAL

日常編程過程中應該見過WARNING和ERROR吧,一個是警告,一個是錯誤

從這些單詞的英文釋義就可以知道每個等級的權重了,DEBUG等級最小,CRITICAL等級最大

還有更詳細的等級分法:DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY

3.4 logging模塊簡介

Python中用來記錄日誌的模塊為:logging,這是一個內置標準庫

logging模塊的日誌等級有5個:DEBUG, INFO, WARNING, ERROR, CRITICAL,另外,logging模塊支持用戶自定義其他等級,但這並不推薦,因為自定義的等級通常會造成等級混亂,極其容易和他人開發的程序衝突。

日誌等級                                等級說明
DEBUG                       詳細信息,通常僅在診斷問題時才有意義
INFO                        詳細程度僅次於DEBUG,確認事情按預期工作。
WARNING                     表示發生了意外情況,或表明在不久的將來出現了一些問題,但該軟件仍在按預期工作。
ERROR                       由於更嚴重的問題,該軟件無法執行某些功能。
CRITICAL                    嚴重錯誤,表明程序本身可能無法繼續運行。

3.5 logging模塊使用方法

3.5.1 日誌基本使用方法

1 import logging
2 
3 logging.debug('My level is debug')
4 logging.info('My level is info')
5 logging.warning('My level is warning')
6 logging.error('My level is error')
7 logging.critical('My level is critical')

上述代碼就是5種不同級別日誌的使用方法,運行一下看看結果如何:

# 運行結果
WARNING:root:My level is warning
ERROR:root:My level is error
CRITICAL:root:My level is critical

這時一定在疑惑,明明是五個等級啊,怎麼只輸出了后三個等級的內容

logging模塊雖然有5的等級,但是他的默認最小等級是WARNING,也就是說,logging模塊會自動忽略WARNING以下的等級,那怎麼才能輸出5個等級的內容呢?

需要在所有輸出語句之前配置日誌,看例:

1 import logging
2 
3 logging.basicConfig(level=logging.DEBUG)   # 配置日誌
4 
5 logging.debug('My level is debug')
6 logging.info('My level is info')
7 logging.warning('My level is warning')
8 logging.error('My level is error')
9 logging.critical('My level is critical')
# 運行結果:
DEBUG:root:My level is debug
INFO:root:My level is info
WARNING:root:My level is warning
ERROR:root:My level is error
CRITICAL:root:My level is critical

那麼問題來了, logging.basicConfig 是個什麼鬼呢?這個其實就是日誌的配置函數,可以配置日誌等級、日誌輸出文件、日誌文件的打開模式(默認是追加)、日誌格式、日期格式等,這些選項對應的參數分別為:level、filename 、filemode、format、datefmt,舉個例子:

1 import logging
2 
3 logging.basicConfig(level=logging.DEBUG, filename='test.log')
4 
5 logging.debug('My level is debug')
6 logging.warning('My level is warning')
7 logging.error('My level is error')

運行之後會發現屏幕並沒有日誌信息,而是在當前目錄下生成一個文件名為‘test.log’的日誌文件,打開看一下這個文件:

DEBUG:root:My level is debug
WARNING:root:My level is warning
ERROR:root:My level is error

可以看到已經將日誌寫入到指定文件了,這樣就可以將日誌保存下來供以後分析利用了。

有的小夥伴會說,自己能不能修改日誌的輸出格式呢?比如想要加入時間,完全沒有問題,這就要使用format參數了,關於format參數的使用,在下面羅列了一張表格供大家參考:

 1 使用格式                             概述
 2 %(asctime)s                    日誌發生時間
 3 %(created)f                    日誌發生時間戳
 4 %(relativeCreated)d            日誌發生時間相對於logging模塊加載時間的相對毫秒
 5 %(msecs)d                      日誌發生時間的毫秒部分
 6 %(levelname)s                  日誌發生的文字等級(debug、info、warning、error、critical)
 7 %(levelno)s                    日誌發生的数字等級(10、20、30、40、50 8 %(name)s                       日誌記錄器名稱(默認是root)
 9 %(message)s                    日誌文本內容
10 %(pathname)s                   調用日誌的源代碼的絕對路徑
11 %(filename)s                   pathname的文件名部分,包括後綴名
12 %(module)s                     filename的文件名部分,不包含後綴名
13 %(lineno)d                     調用日誌的源代碼的行數
14 %(funcName)s                   調用日誌的函數名
15 %(process)d                    進程號
16 %(prscessName)s                進程名
17 %(thread)d                     線程號
18 %(thread)s                     線程名

舉個例子: 

1 import logging
2 
3 LOG_FORMAT = '%(asctime)s - %(levelname)s - %(name)s - %(message)s'
4 logging.basicConfig(level=logging.DEBUG, filename='test.log', format = LOG_FORMAT)
5 
6 logging.debug('My level is debug')
7 logging.warning('My level is warning')
8 logging.error('My level is error')

查看‘test.log’:

DEBUG:root:My level is debug
WARNING:root:My level is warning
ERROR:root:My level is error
2019-03-24 10:52:46,093 - DEBUG - root - My level is debug
2019-03-24 10:52:46,094 - WARNING - root - My level is warning
2019-03-24 10:52:46,094 - ERROR - root - My level is error

可以看到日誌輸出格式明顯發生了改變

注意:打開文件模式默認為追加

如果想要改變時間的輸出格式,需要使用datefmt參數,要注意datefmt參數要在format參數里有時間的前提下才會生效,這裏就不在舉例了,關於時間的格式可以參考time模塊時的講解

3.5.2 日誌高級使用方法

以後再介紹哇,基本的使用方法已經可以滿足使用了,高級使用方法比較複雜,以後再更新

四、Debug方法三:pdb模塊和ipdb模塊

4.1 pdb和ipdb概述

pdb是Python內置的Debug模塊,但是其功能不夠強大,於是便有了第三方模塊ipdb的出現;它們兩個的關係就好像python和ipython的關係。

ipdb調試代碼是比print函數更加高級和靈活的方式,應當熟練應用ipdb的使用方式,並且取代print這種low方法🐔🚄

ipdb不需要入侵源代碼,可以按步執行,可以打斷點,可以在程序運行時查看變量值,可以在程序運行時修改變量值,盤它!

4.2 ipdb安裝

pip install ipdb

『防抄襲:讀者請忽略這段文字,文章作者是博客園的MinuteSheep

4.3 ipdb的使用

學習ipdb,心中要有一張表:

命令                       說明
next或n                   執行下一行
exit或q                   終止程序並退出
p或pp                     打印變量
!                         修改變量
list或l                   查看當前程序代碼
break或b                  設置斷點
continue或c               繼續執行程序直到遇到斷點
step或s                   進入函數
return或r                 在函數中使用,執行代碼直到遇到斷點或者函數結束
help                      幫助

使用ipdb時,可以在代碼內部提前導入ipdb模塊,但這通常是不現實的;通常採取的方法是這樣的,在命令行輸入:

python -m ipdb xxx.py

輸入以上命令后,便會進入ipdb的debug交互模式,接下來開始舉例(多圖警告): 

案例代碼:

 1 # This is a test for ipdb
 2 # Author: MinuteSheep<minutesheep@163.com>
 3 
 4 import time
 5 import math
 6 
 7 
 8 def get_p_time():
 9     present_time = time.asctime()
10     return present_time
11 
12 
13 a = 123
14 b = 456
15 
16 c = a + b
17 
18 print(c)
19 
20 present_time = get_p_time()
21 
22 print(present_time)

案例1: 使用n

在命令輸入 python -m ipdb 4.py 后,會出現如下交互模式: 

《#7 Python代碼調試》

聰明的你已經發現代碼一進入就執行到了第4行,其實這也很好理解,前面3行都是註釋嘛,對代碼的執行並沒有實際作用,ipdb遇到註釋語句會自動跳過的

接下來輸入一個n,讓那個代碼繼續執行一行: 

《#7 Python代碼調試》

可以看到返回的結果在動態的運行,多輸入幾次看看效果哇: 

《#7 Python代碼調試》

執行多次n后,程序按照預計的順序執行

注意:執行到18行遇到print(c),但是並沒有立即返回結果,那是因為箭頭位置是指將要執行這一行了,本次並不會執行,所有下一步才輸出c的值579

注意:程序執行到第8行代碼時,遇到定義函數,這時在執行下一行時,代碼會跳過函數部分,直接來到13行

案例2: 使用exit或q

輸入exit或q便會直接退出🙈

案例3: p或pp

《#7 Python代碼調試》

上面圖片中可以看到使用p或pp打印變量的值

『防抄襲:讀者請忽略這段文字,文章作者是博客園的MinuteSheep

案例4: 使用! 

《#7 Python代碼調試》

由上圖可知:想要修改變量需要在變量前面加一個!

案例5: 使用list或l 

《#7 Python代碼調試》

當你調試代碼過程中忘記了程序執行到哪裡了的時候,可以使用l來查看一下,效果如上圖

案例6: 使用step或s

《#7 Python代碼調試》

當遇到執行函數時,默認會在後台執行完函數並且指向下一行代碼,但是按照我們的思維,當遇到執行函數時,需要返回頭去看看函數時怎麼運行的,想要看看代碼在函數中時如何一步一步運行的,使用s即可: 

《#7 Python代碼調試》

ipdb基本使用方法就這些,還有一點關於斷點的使用,下次補充更新🙉

 

点赞

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *