Published 6月 16, 2020 by with 1 comment

Automate The Boring Stuff With Python - Chapter 10 – 調試



今天是看這本書Automate the Boring Stuff with Python(Python編程快速上手--讓繁瑣工作自動化)
第10章節(Organizing Files)所做的練習

這章主要學習透過日誌, 斷言和調試器, 發現程式碼的問題並修復.


raise 搭配Exception函數調用, 可用來包含有用的出錯訊息
>>> raise Exception('This is the error message')
Traceback (most recent call last):
  File "<pyshell#4>", line 1, in <module>
    raise Exception('This is the error message')
Exception: This is the error message
>>>

先讓我們用個印出方框的程式來學習
>>> def boxprint(symbol, width, height):
        # 印出頂部
        print(symbol * width)
       
        # 印出中間
        for i in range(height - 2):
            print(symbol + (' ' * (width - 2)) + symbol)
       
        # 印出底部
        print(symbol * width)
       
>>> boxprint('*', 15, 5)
***************
*             *
*             *
*             *
***************
>>>


這程式看似完美, 但如果給予的參數有兩個** 或 寬<=2 或 高<=2
程式都能運行, 且不會顯示錯誤. 但結果卻不會是我們想要的方框.
>>> boxprint('**', 1, 2)
**
**
>>>

使用raise 搭配Exception函數調用, 就能拋出異常, 並讓我了解錯誤之處
>>> def boxprint(symbol, width, height):
    # 若參數符號不為1, 則拋出異常.
    if len(symbol) != 1:
        raise Exception('Symbol must be a single character string')
   
    # 若參數寬<=2, 則拋出異常.
    if width <= 2:
        raise Exception('Width must be grester than 2')
   
    # 若參數高<=2, 則拋出異常.
    if height <= 2:
        raise Exception('height must be grester than 2')
       
    # 印出頂部
    print(symbol * width)

    # 印出中間
    for i in range(height - 2):
        print(symbol + (' ' * (width - 2)) + symbol)
   
    # 印出底部
    print(symbol * width)
   
>>> boxprint('**', 5, 5)
Traceback (most recent call last):
  File "<pyshell#73>", line 1, in <module>
    boxprint('**', 5, 5)
  File "<pyshell#72>", line 3, in boxprint
    raise Exception('Symbol must be a single character string')
Exception: Symbol must be a single character string
>>>


Python若遇到程式碼錯誤, 會生成一些錯誤訊息. 稱為反向跟蹤(Trackback)
可以透過調用trackback.format_exec()來得到反向跟蹤的字符串型式.

首先先寫一個會發生錯過的程式碼, 並用raise 搭配Exception函數來產生我們要的錯誤訊息.
其執行結果可以很清楚的看到, 在bacon()的第二行產生了我們要的錯誤訊息
>>> def spam():
    bacon()

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

>>> spam()
Traceback (most recent call last):
  File "<pyshell#103>", line 1, in <module>
    spam()
  File "<pyshell#99>", line 2, in spam
    bacon()
  File "<pyshell#102>", line 2, in bacon
    raise Exception('This is the error message')
Exception: This is the error message
>>>

調用trackback.format_exec()來得到反向跟蹤的字符串型式.
需要先import trackback. 然後再開啟一個errorInfo.txt檔 並把traceback的訊息寫入.
import traceback
try:
    raise Exception('This is the error message')

except:
    errorFile = open('errorInfo.txt', 'w')
    errorFile.write(traceback.format_exc())
    errorFile.close()
    print('The traceback info was written to errorInfo.txt')

def spam():
    bacon()

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

spam()

errorInfo.txt的內容如下:
Traceback (most recent call last):
  File "D:/Tech/Blog/Python/Automate The Boring Stuff With Python/Chapter 10/10_2.py", line 3, in <module>
    raise Exception('This is the error message')
Exception: This is the error message


斷言(Assert), 就是設定一個條件為真, 當這條件不為真時.
雖然程式沒有崩潰, 但還是有缺陷. 提示出字符串
首先寫一個交通燈程式:
街道1和街道2的十字路口, 有南北向與東西向的交通燈.
交通燈的順序為綠>黃>紅.
street1_street2 = {'ns': 'green', 'ew': 'red'}


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'


print('First Time: ' + str(street1_street2))
switchlights(street1_street2)
print('Second Time: ' + str(street1_street2))

---------------其運行結果為:---------------
First Time: {'ns': 'green', 'ew': 'red'}
Second Time: {'ns': 'yellow', 'ew': 'green'}

可以發現第一次,很正常. 南北向為綠燈, 東西向為紅燈.
第二次就有問題. 南北向不應該為黃燈, 東西向為綠燈.
雖然程式沒有崩潰, 但這結果不是我想要的(當東西向為綠燈時, 南北向應為紅燈, 不該為黃燈)

所以我們加入斷言來檢查, 如果變數stoplight中沒有red之值, 便指出程式碼錯誤.
並產生字浮串(Neither light is red! ' + str(stoplight))
street1_street2 = {'ns': 'green', 'ew': 'red'}


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(), 'Neither light is red! ' + str(stoplight)


print('First Time: ' + str(street1_street2))
switchlights(street1_street2)
print('Second Time: ' + str(street1_street2))


---------------其運行結果為:---------------
First Time: {'ns': 'green', 'ew': 'red'}
Traceback (most recent call last):
  File "D:/Tech/Blog/Python/Automate The Boring Stuff With Python/Chapter 10/10_3.py", line 17, in <module>
    switchlights(street1_street2)
  File "D:/Tech/Blog/Python/Automate The Boring Stuff With Python/Chapter 10/10_3.py", line 12, in switchlights
    assert 'red' in stoplight.values(), 'Neither light is red! ' + str(stoplight)
AssertionError: Neither light is red! {'ns': 'yellow', 'ew': 'green'}


Python的Logging模組可以很容易創造一個自定義的消息紀錄.
要啟用Logging模組要先輸入下列程式碼
import logging
logging .basicConfig(level=logging.DEBUG, format=' %(asctime)s - %(levelname)s - %(message)s')


然後寫一個計算價乘的程式, 比如1*2*3*4*5等於多少
並放入logging訊息
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))
    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')


---------------其運行結果為:---------------
0
 2020-06-16 15:32:43,875 - DEBUG - Start of program
 2020-06-16 15:32:43,875 - DEBUG - Start of factorial(5)
 2020-06-16 15:32:43,875 - DEBUG - i is 0, total is 0
 2020-06-16 15:32:43,875 - DEBUG - i is 1, total is 0
 2020-06-16 15:32:43,875 - DEBUG - i is 2, total is 0
 2020-06-16 15:32:43,875 - DEBUG - i is 3, total is 0
 2020-06-16 15:32:43,875 - DEBUG - i is 4, total is 0
 2020-06-16 15:32:43,875 - DEBUG - i is 5, total is 0
 2020-06-16 15:32:43,875 - DEBUG - End of factorial(5)
 2020-06-16 15:32:43,875 - DEBUG - End of program


可以發現1*2*3*4*5=0 這是不對的
透過Logging 可以發現 一開始i為0 就不對, 應該把i改為1
for i in range(1, n + 1):
---------------其運行結果為:---------------
 2020-06-16 15:38:12,141 - DEBUG - Start of program
 2020-06-16 15:38:12,141 - DEBUG - Start of factorial(5)
 2020-06-16 15:38:12,141 - DEBUG - i is 1, total is 1
 2020-06-16 15:38:12,141 - DEBUG - i is 2, total is 2
 2020-06-16 15:38:12,141 - DEBUG - i is 3, total is 6
 2020-06-16 15:38:12,141 - DEBUG - i is 4, total is 24
 2020-06-16 15:38:12,142 - DEBUG - i is 5, total is 120
 2020-06-16 15:38:12,142 - DEBUG - End of factorial(5)
 2020-06-16 15:38:12,142 - DEBUG - End of program
120


Logging有五個級別, 由低至高分別為
debug, info, warning, error, critical
可以透過設定logging level來過濾所需要的函數
比如
level = logging.ERROR
這將只顯示error及更高級別的critical, 而跳過其他三個較低級別的訊息

還有一個方法可以做到相同的事
logging.disable(logging.ERROR)
這樣可以停止顯示error及其較低等級的訊息

logging.basicConfig(filename='myProgramLog.txt')
可以將日誌寫入一個文檔存起來

在Python IDLE的Debug > Debugger 可以打開Python的調試器


Go: 可以讓程式一次正常執行到最後
Step: 程式一行行的執行
Over: 跟Step類似, 但會跳過函數調用
Out: 當用step執行並進入入了函數, Out可以直接執行完整個函數調用, 直到從函數返回
Quit: 停止Debugging, 並跳出調試器

章節10習題, 用Notepad++打下列的程式碼,
另存為10_8.py. 我附上中文注釋方便好讀.
# -*- coding: UTF-8 -*-
# http://juilin77.blogspot.com/
# v20200616
# Automate The Boring Stuff With Python - Chapter 10

# ========== 10.8 ==========
import random

guess = input('Guess the coin toss! Enter heads or tails:')
if guess != 'heads' and guess != 'tails':
    raise Exception('Must be heads or tails')


guess_toss = {0: 'heads', 1: 'tails'}
toss = guess_toss[random.randint(0, 1)]  # 0 is tails, 1 is heads

if toss == guess:
    print('You got it!')
else:
    guess = input('Nope! Guess again!')
    if toss == guess:
        print('You got it!')
    else:
        print('Nope. You are really bad at this game.')


Reference:
Automate the Boring Stuff with Python
Python編程快速上手--讓繁瑣工作自動化
ISBN-10: B01N6B9BSA
https://www.amazon.com/Python%E7%BC%96%E7%A8%8B%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B-%E8%AE%A9%E7%B9%81%E7%90%90%E5%B7%A5%E4%BD%9C%E8%87%AA%E5%8A%A8%E5%8C%96-%E7%BE%8E-Al-Sweigart%EF%BC%88%E6%96%AF%E7%BB%B4%E5%8A%A0%E7%89%B9%EF%BC%89/dp/B01I0XN8XY/ref=sr_1_1?ie=UTF8&qid=1543814481&sr=8-1&keywords=9787115422699

官網:
https://automatetheboringstuff.com/


最初發表 / 最後更新: 2020.06.16 / 2020.06.16

1 則留言: