Python 函式是組織程式碼的基礎單元,有效運用函式能提升程式碼的可讀性和重用性。定義函式使用 def 關鍵字,函式呼叫後會傳回特定值,若無明確傳回則預設為 None。函式作用域的變數在函式傳回後會被銷毀。全域變數可透過 global 關鍵字在函式內部修改。錯誤處理方面,try-except 區塊能捕捉特定錯誤,避免程式當機,而 raise 陳述式則允許自訂錯誤訊息。日誌記錄和除錯工具在開發過程中至關重要,logging 模組能記錄不同層級的訊息,而 debugger 則能逐步執行程式碼,協助找出錯誤。理解這些技巧能有效提升程式碼的健壯性和除錯效率,是 Python 開發的必備技能。

練習題解答

  1. 函式之所以有優勢,是因為它們可以將程式碼組織成邏輯群組,從而提高程式的可讀性和可維護性。另外,函式還可以避免程式碼冗餘,提高程式碼的重用性。

  2. 函式的程式碼是在函式被呼叫時執行,而不是在函式被定義時。

  3. def陳述式用於建立一個函式。

  4. 函式是一段可以多次呼叫的程式碼塊,而函式呼叫則是指在程式中某個位置執行這個函式的動作。

  5. 在Python程式中,只有一個全域範圍,而區域性範圍的數量取決於定義的函式數量。每個函式都有一個自己的區域性範圍。

6. 當函式呼叫傳回時,區域性範圍的變數會發生什麼?

當函式呼叫傳回時,區域性範圍的變數會被銷毀,不再存在。

7. 什麼是傳回值?傳回值可以是表示式的一部分嗎?

傳回值是函式執行後傳回的結果,可以是任何型別的值,包括整數、字串等。傳回值可以用於表示式中,例如 x = foo() + 1,其中 foo() 是一個傳回整數的函式。

8. 如果函式沒有傳回陳述式,則呼叫該函式的傳回值是什麼?

如果函式沒有傳回陳述式,則呼叫該函式的傳回值為 None

9. 如何強制函式中的變數參考全域變數?

使用 global 關鍵字可以強制函式中的變數參考全域變數,例如 global x

10. None 的資料型別是什麼?

None 的資料型別是 NoneType

11. import areallyourpetsnamederic 陳述式做了什麼?

這個陳述式並不合法,可能是打字錯誤或無效的模組名稱。

12. 如果你有一個名為 bacon() 的函式在一個名為 spam 的模組中,如何在匯入 spam 後呼叫它?

可以使用 spam.bacon() 呼叫函式。

13. 如何防止程式在遇到錯誤時當機?

可以使用 try-except 區塊來捕捉錯誤,並在 except 區塊中處理錯誤。

14. try 區塊中放什麼?except 區塊中放什麼?

try 區塊中放可能發生錯誤的程式碼,except 區塊中放錯誤處理程式碼。

15. 撰寫一個名為 notrandomdice.py 的程式,並執行它。為什麼每次函式呼叫都傳回相同的數字?

import random
random_number = random.randint(1, 6)
def get_random_dice_roll():
    return random_number

print(get_random_dice_roll())
print(get_random_dice_roll())
print(get_random_dice_roll())
print(get_random_dice_roll())

每次函式呼叫都傳回相同的數字,因為 random_number 只在程式啟動時生成一次,並不在每次函式呼叫時重新生成。

練習程式

Collatz 序列

def collatz(number):
    if number % 2 == 0:
        print(number // 2, end=' ')
        return number // 2
    else:
        print(3 * number + 1, end=' ')
        return 3 * number + 1

number = int(input("Enter number: "))
while number!= 1:
    number = collatz(number)

這個程式會不斷呼叫 collatz() 函式,直到傳回值為 1。

使用 try-except 處理非整數輸入

在處理使用者輸入時,經常會遇到非整數輸入的情況。為了處理這種情況,我們可以使用 try-except 陳述式來捕捉 ValueError 錯誤。

範例程式碼

def get_integer_input():
    while True:
        try:
            user_input = int(input("請輸入一個整數:"))
            return user_input
        except ValueError:
            print("您輸入的不是整數,請再試一次。")

# 測試程式碼
user_input = get_integer_input()
print("您輸入的整數是:", user_input)

在這個範例中,我們定義了一個 get_integer_input 函式,該函式使用 while 迴圈不斷要求使用者輸入整數,直到使用者輸入有效的整數。當使用者輸入非整數時,程式碼會捕捉 ValueError 錯誤,並印出錯誤訊息,然後再次要求使用者輸入。

使用 raise 陳述式自訂錯誤訊息

在某些情況下,我們可能需要自訂錯誤訊息,以便更好地瞭解程式執行中的錯誤。為此,我們可以使用 raise 陳述式來引發自訂的錯誤。

範例程式碼

def divide(a, b):
    if b == 0:
        raise ValueError("除數不能為零")
    return a / b

# 測試程式碼
try:
    result = divide(10, 0)
    print("結果:", result)
except ValueError as e:
    print("錯誤:", e)

在這個範例中,我們定義了一個 divide 函式,該函式在除數為零時引發 ValueError 錯誤,並自訂錯誤訊息。當我們呼叫 divide 函式時,如果除數為零,程式碼會捕捉 ValueError 錯誤,並印出自訂的錯誤訊息。

除錯工具和技術

除錯是程式設計中非常重要的一部分。透過使用適當的工具和技術,我們可以更快速、更有效地找到並修復程式中的錯誤。

使用 debugger

Debugger 是一個強大的工具,允許我們一步一步地執行程式,並在執行過程中檢查變數的值。這樣,我們就可以更好地瞭解程式的執行流程和錯誤的原因。

範例程式碼

def add(a, b):
    result = a + b
    return result

# 測試程式碼
result = add(2, 3)
print("結果:", result)

在這個範例中,我們可以使用 debugger 步步執行 add 函式,並檢查變數 result 的值,以便更好地瞭解程式的執行流程。

logging 和 assertions

Logging 和 assertions 是兩種有用的工具,可以幫助我們偵測和修復程式中的錯誤。

範例程式碼

import logging

def divide(a, b):
    if b == 0:
        logging.error("除數不能為零")
        return None
    return a / b

# 測試程式碼
result = divide(10, 0)
if result is None:
    print("錯誤:除數不能為零")

在這個範例中,我們使用 logging 來記錄錯誤訊息,並使用 assertions 來檢查變數的值。如果除數為零,程式碼會記錄錯誤訊息並傳回 None

自定義盒子印刷函式

def 自定義盒子印刷(符號, 寬度, 高度):
    """
    這個函式用於印刷一個自定義的盒子。
    
    引數:
    符號 (str): 盒子的邊框符號,必須是一個單一字元。
    寬度 (int): 盒子的寬度,必須大於 2。
    高度 (int): 盒子的高度,必須大於 2。
    
    Raises:
    Exception: 如果符號不是一個單一字元,或者寬度或高度不大於 2。
    """
    
    # 檢查符號是否是一個單一字元
    if len(符號)!= 1:
        raise Exception('符號必須是一個單一字元')
        
    # 檢查寬度是否大於 2
    if 寬度 <= 2:
        raise Exception('寬度必須大於 2')
        
    # 檢查高度是否大於 2
    if 高度 <= 2:
        raise Exception('高度必須大於 2')
        
    # 印刷盒子的上邊框
    print(符號 * 寬度)
    
    # 印刷盒子的內容
    for i in range(高度 - 2):
        print(符號 + (' ' * (寬度 - 2)) + 符號)
        
    # 印刷盒子的下邊框
    print(符號 * 寬度)

try:
    自定義盒子印刷('*', 4, 4)
    自定義盒子印刷('O', 20, 5)
    自定義盒子印刷('x', 1, 3)
except Exception as 錯誤:
    print(f"發生錯誤:{錯誤}")

內容解密:

這個程式碼定義了一個名為 自定義盒子印刷 的函式,該函式用於印刷一個自定義的盒子。函式接受三個引數:符號寬度高度。函式首先檢查 符號 是否是一個單一字元,如果不是,則引發一個 Exception。然後,函式檢查 寬度高度 是否大於 2,如果不是,則引發一個 Exception。如果所有檢查都透過,函式就會印刷出一個盒子,盒子的上邊框和下邊框由 符號 組成,內容由空格組成。

圖表翻譯:

@startuml
:開始; --> :檢查符號;
:B; --> :檢查寬度;
:C; --> :檢查高度;
:D; --> :印刷盒子;
:E; --> :結束;
@enduml

這個流程圖展示了 自定義盒子印刷 函式的執行流程。首先,函式檢查 符號 是否是一個單一字元,如果不是,則引發一個 Exception。然後,函式檢查 寬度高度 是否大於 2,如果不是,則引發一個 Exception。如果所有檢查都透過,函式就會印刷出一個盒子。

錯誤處理和斷言

在程式設計中,錯誤處理和斷言是兩個非常重要的概念。錯誤處理允許我們在程式執行時發生錯誤時進行捕捉和處理,而斷言則是我們用來檢查程式是否正確執行的工具。

錯誤處理

Python 中的 tryexcept 陳述式可以用來處理錯誤。當我們預期某段程式碼可能會發生錯誤時,我們可以將其放在 try 區塊中,並使用 except 區塊來捕捉和處理這些錯誤。

try:
    box_print('ZZ', 3, 3)
except Exception as err:
    print('An exception happened: ' + str(err))

在這個例子中,如果 box_print 函式發生任何錯誤,錯誤訊息將被捕捉並印出。

斷言

斷言是用來檢查程式是否正確執行的工具。它們可以用來檢查某些條件是否為真,如果條件不為真,則會引發 AssertionError 例外。

assert condition, "Assertion failed"

斷言通常用於檢查函式的輸入引數、變數的值等是否正確。例如:

def add(a, b):
    assert isinstance(a, (int, float)), "a must be a number"
    assert isinstance(b, (int, float)), "b must be a number"
    return a + b

在這個例子中,如果 ab 不是數字,則會引發 AssertionError 例外。

錯誤處理和日誌記錄

在 Python 中,錯誤處理和日誌記錄是兩個非常重要的概念。錯誤處理可以幫助我們捕捉和處理程式執行中的錯誤,而日誌記錄可以幫助我們追蹤程式的執行過程和錯誤資訊。

錯誤處理

Python 中的 assert 陳述式可以用來檢查程式中的條件是否正確,如果條件不正確,則會引發一個 AssertionError。例如:

ages = [26, 57, 92, 54, 22, 15, 17, 80, 47, 73]
ages.sort()
assert ages[0] <= ages[-1]

如果 ages 列表中的第一個元素大於最後一個元素,則會引發一個 AssertionError

日誌記錄

Python 中的 logging 模組可以用來記錄程式的執行過程和錯誤資訊。例如:

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

這會設定日誌記錄的級別為 DEBUG,並指定日誌記錄的格式。然後,我們可以使用 logging.debug() 函式來記錄程式的執行過程。

範例

下面是一個範例程式,示範如何使用 assertlogging

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

def factorial(n):
    logging.debug('Start of factorial(' + str(n) + ')')
    if n < 0:
        logging.error('n must be a non-negative integer')
        return None
    elif n == 0:
        logging.debug('Base case: n = 0')
        return 1
    else:
        logging.debug('Recursive case: n = ' + str(n))
        return n * factorial(n-1)

logging.debug('Start of program')
result = factorial(5)
logging.debug('Result: ' + str(result))

這個程式會計算 5 的階乘,並記錄程式的執行過程和錯誤資訊。

內容解密:

  • assert 陳述式可以用來檢查程式中的條件是否正確。
  • logging 模組可以用來記錄程式的執行過程和錯誤資訊。
  • 日誌記錄的級別可以設定為 DEBUGINFOWARNINGERRORCRITICAL
  • 日誌記錄的格式可以指定為 %(asctime)s - %(levelname)s - %(message)s

圖表翻譯:

@startuml
:Start of program; --> :Calculate factorial;
:B; --> :Check if n is non-negative;
:C; --> :Base case: n = 0;
:C; --> :E;
:E; --> :Calculate factorial;
:F; --> :Return result;
:G; --> :End of program;
@enduml

這個圖表示範了程式的執行過程和控制流程。

使用logging模組進行除錯

在Python中,logging模組是一種強大的工具,能夠幫助我們進行除錯和記錄程式的執行過程。下面是一個簡單的範例,展示如何使用logging模組:

import logging

# 設定logging模組的基本組態
logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s - %(levelname)s - %(message)s')

# 定義一個函式,計算階乘
def factorial(n):
    total = 1
    for i in range(1, n + 1):
        total *= i
        logging.debug('i is %d, total is %d', i, total)
    logging.debug('End of factorial(%d)', n)
    return total

# 測試函式
print(factorial(5))

# 記錄程式的結束
logging.debug('End of program')

這個範例中,我們使用logging模組來記錄函式factorial的執行過程。logging模組會自動記錄每個log訊息的時間、等級和內容。

將log訊息寫入檔案

如果你想將log訊息寫入檔案,而不是顯示在螢幕上,你可以使用filename引數來指設定檔名:

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

這樣,log訊息就會被寫入myProgramLog.txt檔案中。

使用logging模組的優點

使用logging模組有以下優點:

  • 可以輕鬆地在程式中新增log訊息
  • 可以根據需要顯示或隱藏log訊息
  • 可以將log訊息寫入檔案中

常見的錯誤

有一些常見的錯誤,可能會導致程式出現問題。例如,以下程式碼中,for迴圈的初始值設定為0,而不是1:

for i in range(n + 1):
    total *= i

這會導致函式factorial傳回錯誤的結果。正確的程式碼應該是:

for i in range(1, n + 1):
    total *= i

使用logging模組可以幫助我們快速地發現這種問題。

最佳實踐

在使用logging模組時,以下是一些最佳實踐:

  • 使用logging.debug()來記錄程式的執行過程
  • 使用logging.info()來記錄重要的事件
  • 使用logging.warning()來記錄可能的問題
  • 使用logging.error()來記錄錯誤
  • 使用logging.critical()來記錄嚴重的錯誤

透過遵循這些最佳實踐,你可以有效地使用logging模組來除錯和記錄你的程式。

訊息記錄層級

訊息記錄層級(Logging Levels)提供了一種方法來根據訊息的重要性對其進行分類別。Python 中有五個層級的訊息記錄,從最不重要到最重要,分別是:除錯(DEBUG)、資訊(INFO)、警告(WARNING)、錯誤(ERROR)和關鍵(CRITICAL)。

訊息記錄層級表

層級 訊息記錄函式 描述
DEBUG logging.debug() 最低層級,用於記錄小細節,通常只在診斷問題時才需要。
INFO logging.info() 用於記錄一般事件或確認程式在各個點的執行狀態。
WARNING logging.warning() 用於指示可能導致未來問題的潛在問題,但不會立即導致程式失敗。
ERROR logging.error() 用於記錄導致程式失敗的錯誤。
CRITICAL logging.critical() 最高層級,用於指示導致程式完全停止執行的嚴重錯誤。

訊息記錄範例

import logging

# 設定基本組態
logging.basicConfig(level=logging.DEBUG, format='%(levelname)s - %(message)s')

# 記錄除錯訊息
logging.debug('一些小細節和除錯訊息')

# 記錄一般事件
logging.info('一個事件發生了')

# 記錄警告訊息
logging.warning('可能導致未來問題的潛在問題')

# 記錄錯誤訊息
logging.error('導致程式失敗的錯誤')

# 記錄關鍵錯誤訊息
logging.critical('導致程式完全停止執行的嚴重錯誤')

內容解密:

在上述範例中,我們使用 logging.basicConfig() 函式設定基本組態,包括設定層級為 DEBUG 和格式為 '%(levelname)s - %(message)s'。然後,我們使用不同的訊息記錄函式(debug()、info()、warning()、error()、critical())記錄不同層級的訊息。

圖表翻譯:

@startuml
:設定基本組態; --> :記錄除錯訊息;
:B; --> :記錄一般事件;
:C; --> :記錄警告訊息;
:D; --> :記錄錯誤訊息;
:E; --> :記錄關鍵錯誤訊息;
@enduml

在這個流程圖中,我們展示了訊息記錄的流程,從設定基本組態開始,然後記錄不同層級的訊息,最終到達記錄關鍵錯誤訊息的階段。

使用logging模組進行錯誤追蹤

Python的logging模組是一個強大的工具,能夠幫助您追蹤和管理程式中的錯誤。它提供了五個不同的logging等級:DEBUG、INFO、WARNING、ERROR和CRITICAL。每個等級都對應著不同的錯誤嚴重程度。

logging等級

  • DEBUG:此等級用於除錯目的,提供最詳細的資訊。
  • INFO:此等級用於提供一般性的資訊。
  • WARNING:此等級用於警告可能出現的問題。
  • ERROR:此等級用於表示已經發生的錯誤。
  • CRITICAL:此等級用於表示嚴重的錯誤,可能會導致程式當機。

使用logging模組

要使用logging模組,首先需要匯入它。然後,您可以設定logging等級和格式。例如:

import logging

logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')

這裡,我們設定了logging等級為INFO,並定義了格式為'%(levelname)s - %(message)s'

訊息輸出

您可以使用不同的logging函式來輸出不同等級的訊息。例如:

logging.debug('This is a debug message.')
logging.info('This is an info message.')
logging.warning('This is a warning message.')
logging.error('This is an error message.')
logging.critical('This is a critical message.')

關閉logging

如果您不想看到某些等級的訊息,您可以使用logging.disable()函式來關閉它們。例如:

logging.disable(logging.CRITICAL)

這裡,我們關閉了CRITICAL等級的訊息。

Mu的Debugger

Mu是一個整合開發環境(IDE),它提供了一個強大的Debugger工具。Debugger允許您一步一步地執行您的程式,檢視變數的值和程式的執行流程。

啟動Debugger

要啟動Debugger,您需要在Mu中點選"Debug"按鈕。然後,Debugger會暫停您的程式的執行,並顯示當前的變數值和程式的執行流程。

Debugger按鈕

Mu的Debugger提供了幾個按鈕,允許您控制程式的執行:

  • Continue:繼續執行程式,直到遇到斷點或程式結束。
  • Step Over:執行下一行程式碼,如果是函式呼叫,則跳過函式內的程式碼。
  • Step In:執行下一行程式碼,如果是函式呼叫,則進入函式內的程式碼。
  • Step Out:執行程式碼,直到傳回當前的函式。
  • Stop:停止程式的執行。

範例程式

以下是一個簡單的範例程式,示範如何使用Mu的Debugger:

print('Enter the first number to add:')
first = input()

print('Enter the second number to add:')
second = input()

您可以在Mu中建立一個新檔案,複製這個程式碼,然後啟動Debugger來追蹤它的執行流程。

程式錯誤分析與除錯

問題描述

有一個簡單的程式,要求使用者輸入三個數字,並計算這三個數字的總和。然而,程式執行後發現結果不正確。

程式碼

print('Enter the first number to add:')
first = input()

print('Enter the second number to add:')
second = input()

print('Enter the third number to add:')
third = input()

print('The sum is ' + first + second + third)

問題分析

程式使用 input() 函式讀取使用者的輸入,但是 input() 函式傳回的是字串,而不是整數。因此,當程式嘗試計算總和時,其實是在串接字串,而不是進行數字加法。

解決方案

為瞭解決這個問題,我們需要將使用者的輸入轉換為整數。可以使用 int() 函式來實作這一點。

print('Enter the first number to add:')
first = int(input())

print('Enter the second number to add:')
second = int(input())

print('Enter the third number to add:')
third = int(input())

print('The sum is', first + second + third)

除錯工具

Mu 編輯器中的除錯工具可以幫助我們找出程式中的錯誤。透過設定斷點和單步執行程式,我們可以觀察變數的值和程式的執行流程。

斷點設定

斷點是程式中的一個位置,當程式執行到該位置時,除錯工具會暫停程式的執行。這樣,我們就可以觀察變數的值和程式的執行流程。

範例:硬幣翻轉模擬

下面的程式模擬翻轉硬幣 1000 次,並計算正面的次數。

import random

heads = 0

for i in range(1, 1001):
    if random.randint(0, 1) == 1:
        heads = heads + 1
    if i == 500:
        print('Halfway done!')

透過設定斷點和單步執行程式,我們可以觀察變數 heads 的值和程式的執行流程。

圖表翻譯

@startuml
:開始; --> :輸入第一個數字;
:B; --> :輸入第二個數字;
:C; --> :輸入第三個數字;
:D; --> :計算總和;
:E; --> :輸出結果;
@enduml

此圖表展示了程式的執行流程,從輸入第一個數字到輸出結果。

程式除錯工具

程式除錯是軟體開發中的一個重要步驟,透過使用適當的工具和技術,可以快速地找出和修復程式中的錯誤。Python 提供了多種工具和技術來幫助開發者進行程式除錯,包括斷言(assert)、異常(exception)、日誌記錄(logging)和偵錯程式(debugger)。

斷言(Assert)

斷言是一種用於檢查程式中某些條件是否成立的工具,如果條件不成立,則會引發一個 AssertionError。斷言通常用於檢查程式中的邏輯是否正確,例如檢查某個變數是否為空或某個函式是否傳回了預期的結果。

assert spam >= 10, 'spam must be 10 or greater'

異常(Exception)

異常是程式中的一種錯誤處理機制,當程式出現錯誤時,可以引發一個異常,然後使用 try-except 塊來捕捉和處理異常。

try:
    # 程式碼
except Exception as e:
    # 處理異常

日誌記錄(Logging)

日誌記錄是一種用於記錄程式執行過程中重要事件的工具,可以幫助開發者瞭解程式的執行狀態和找出錯誤。Python 的 logging 模組提供了多種日誌記錄級別,包括 debug、info、warning、error 和 critical。

import logging

logging.basicConfig(level=logging.DEBUG)
logging.debug('This is a debug message')

偵錯程式(Debugger)

偵錯程式是一種用於一步一步地執行程式和檢查變數值的工具,可以幫助開發者瞭解程式的執行過程和找出錯誤。Python 的 pdb 模組提供了一個基本的偵錯程式,可以使用 step、next 和 continue 等命令來控制程式的執行。

import pdb

pdb.set_trace()

練習題

  1. 寫一個斷言陳述式,當變數 spam 小於 10 時引發 AssertionError。
  2. 寫一個斷言陳述式,當變數 eggs 和 bacon 的值相同(忽略大小寫)時引發 AssertionError。
  3. 寫一個斷言陳述式,總是引發 AssertionError。
  4. 要呼叫 logging.debug() 需要在程式中新增哪兩行程式碼?
  5. 要使 logging.debug() 將日誌訊息傳送到檔案 programLog.txt 需要在程式中新增哪兩行程式碼?
  6. 有哪五個日誌記錄級別?
  7. 可以新增哪一行程式碼來停用所有日誌訊息?
  8. 為什麼使用日誌訊息比使用 print() 顯示相同的訊息更好?
  9. Step Over、Step In 和 Step Out 按鈕在偵錯程式中的區別是什麼?
  10. 在點選 Continue 後,偵錯程式什麼時候會停止?
  11. 什麼是斷點?
  12. 如何在 Mu 中設定斷點?

從技術架構視角來看,Python 的函式和模組機制為程式設計提供了強大的組織和抽象能力。深入剖析函式的區域性作用域、傳回值機制以及與全域變數的互動方式,可以發現這些特性有效提升了程式碼的可讀性、可維護性和可重用性。然而,錯誤處理和除錯仍然是開發過程中不可避免的挑戰。透過 try-except 區塊、斷言以及 logging 模組的靈活運用,開發者可以有效地捕捉、處理和追蹤程式錯誤,提升程式碼的健壯性。展望未來,更進階的除錯工具和技術,例如整合式開發環境(IDE)提供的圖形化除錯介面和自動化測試框架,將進一步簡化除錯流程,提升開發效率。對於追求程式碼品質的開發者而言,持續學習和應用這些工具和技術至關重要。玄貓認為,掌握紮實的錯誤處理和除錯技巧是成為一名優秀 Python 程式設計師的必經之路。