Python 函式的變數作用域是程式設計的基礎,理解其運作原理對於撰寫穩健的程式碼至關重要。變數作用域決定了變數在程式碼不同部分的可見性和生命週期,正確地使用全域和區域性變數可以避免程式錯誤並提高程式碼的可讀性。同時,Python 的例外處理機制能夠有效地捕捉和處理程式執行過程中發生的錯誤,避免程式當機,增強程式的容錯能力。try-except 語法是 Python 例外處理的核心,它允許開發者預先定義錯誤處理邏輯,確保程式在遇到異常情況時也能正常執行或以友好的方式離開。

函式中的變數作用域

在 Python 中,函式中的變數可以是區域性的(local)或全域的(global)。瞭解變數作用域對於撰寫正確的程式碼至關重要。

變數作用域規則

  1. 全域變數:如果函式中使用 global 陳述式宣告一個變數,那麼這個變數就是全域變數。
  2. 區域性變數:如果函式中有一個變數被指定(即出現在等號左邊),那麼這個變數就是區域性變數。
  3. 隱含全域變數:如果函式中使用了一個變數,但沒有對其進行指定,也沒有使用 global 陳述式,那麼這個變數就是全域變數。

範例程式

下面的程式碼示範了變數作用域的規則:

def spam():
    global eggs  # 宣告 eggs 為全域變數
    eggs = 'spam'  # 對全域變數 eggs 進行指定

def bacon():
    eggs = 'bacon'  # 對區域性變數 eggs 進行指定

def ham():
    print(eggs)  # 使用全域變數 eggs

# 執行程式
spam()
print(eggs)  # 輸出:spam

在這個範例中,spam() 函式中使用 global 陳述式宣告 eggs 為全域變數,因此對 eggs 的指定會影響全域變數。bacon() 函式中對 eggs 進行指定,因此 eggs 是區域性變數。ham() 函式中使用全域變數 eggs

錯誤範例

如果你嘗試在函式中使用一個區域性變數之前沒有對其進行指定,Python 會報錯。下面的程式碼示範了這種情況:

def spam():
    print(eggs)  # 錯誤!區域性變數 eggs 沒有被指定
    eggs = 'spam local'

eggs = 'global'
spam()  # 報錯:UnboundLocalError

這個錯誤發生是因為 Python 見到 spam() 函式中有一個對 eggs 的指定,因此認為 eggs 是區域性變數。但是,由於 print(eggs) 在指定之前被執行,因此區域性變數 eggs 尚未被建立。

函式作為「黑盒子」

通常,你只需要知道函式的輸入(引數)和輸出值,而不需要關心函式的實際實作細節。這種思考方式可以幫助你更好地理解和使用函式。

處理Python程式中的錯誤與異常

在Python程式設計中,錯誤與異常的處理是一個非常重要的議題。當程式執行時,可能會遇到各種錯誤或異常,例如除以零的錯誤、變數未定義的錯誤等。這些錯誤如果不被正確處理,可能會導致程式當機或產生不可預期的結果。

什麼是異常?

異常(Exception)是指在程式執行過程中發生的非正常情況。異常可以是由於程式設計中的錯誤,也可以是由於外部因素引起的,例如使用者輸入錯誤、檔案不存在等。

Python中的異常型別

Python中有許多種型別的異常,包括:

  • ZeroDivisionError:除以零的錯誤。
  • NameError:變數未定義的錯誤。
  • TypeError:型別錯誤。
  • FileNotFoundError:檔案不存在的錯誤。

如何處理異常?

Python提供了try-except語法來處理異常。基本語法如下:

try:
    # 可能發生異常的程式碼
except 異常型別:
    # 處理異常的程式碼

範例:處理除以零的錯誤

下面是一個範例,示範如何處理除以零的錯誤:

def spam(divide_by):
    try:
        return 42 / divide_by
    except ZeroDivisionError:
        print("錯誤:除以零!")

print(spam(2))  # 輸出:21.0
print(spam(12))  # 輸出:3.5
print(spam(0))  # 輸出:錯誤:除以零!
print(spam(1))  # 輸出:42.0

在這個範例中,我們定義了一個函式spam(),它接受一個引數divide_by。在函式內部,我們使用try-except語法來處理除以零的錯誤。如果發生ZeroDivisionError`,我們印出一條錯誤訊息。

圖表翻譯:

這個圖表示範了程式的流程:首先呼叫spam()函式,然後檢查是否發生除以零的錯誤。如果發生錯誤,則印出錯誤訊息;否則,計算結果並印出結果。

處理除以零的錯誤

在 Python 中,當你嘗試將一個數字除以零時,會引發一個 ZeroDivisionError。這是一種特殊的錯誤,指出你不能將一個數字除以零。為了處理這種情況,你可以使用 tryexcept 陳述式。

try-except 結構

tryexcept 陳述式用於處理程式碼中的錯誤。try 區塊包含可能引發錯誤的程式碼,而 except 區塊包含當錯誤發生時要執行的程式碼。

def spam(divide_by):
    try:
        # 這裡的程式碼可能會引發 ZeroDivisionError
        return 42 / divide_by
    except ZeroDivisionError:
        # 如果 ZeroDivisionError 發生,則執行這裡的程式碼
        print('Error: Invalid argument.')

處理除以零的錯誤

在上面的例子中,我們定義了一個函式 spam,它接受一個引數 divide_by。在 try 區塊中,我們嘗試將 42 除以 divide_by。如果 divide_by 是零,則會引發 ZeroDivisionError。在 except 區塊中,我們捕捉這個錯誤,並印出一條錯誤訊息。

print(spam(2))  # 輸出:21.0
print(spam(12))  # 輸出:3.5
print(spam(0))  # 輸出:Error: Invalid argument.
print(spam(1))  # 輸出:42.0

在這個例子中,當我們呼叫 spam(0) 時,會引發 ZeroDivisionError,然後執行 except 區塊中的程式碼,印出錯誤訊息。其他呼叫則正常執行,傳回計算結果。

錯誤處理與動畫程式

在 Python 中,錯誤處理是一個非常重要的概念。它可以幫助我們預防程式因為錯誤而終止執行。讓我們來看看一個簡單的例子。

錯誤處理

首先,我們定義了一個函式 spam(divide_by),它會將 42 除以輸入的數值。當我們呼叫這個函式時,如果輸入的數值為 0,就會發生 ZeroDivisionError。

def spam(divide_by):
    return 42 / divide_by

try:
    print(spam(2))
    print(spam(12))
    print(spam(0))
    print(spam(1))
except ZeroDivisionError:
    print('Error: Invalid argument.')

當程式執行到 print(spam(0)) 時,因為除以 0 會發生錯誤,所以程式會跳到 except 區塊,並印出 “Error: Invalid argument."。之後,程式就會繼續執行,不會回到 try 區塊。

動畫程式:Zigzag

現在,我們來建立一個簡單的動畫程式,稱為 Zigzag。這個程式會不斷地印出 “*” 字元,形成一個來回移動的圖案。

import time
import sys

def zigzag():
    while True:
        for i in range(10):
            print("*" * i)
            time.sleep(0.1)
        for i in range(10, 0, -1):
            print("*" * i)
            time.sleep(0.1)

try:
    zigzag()
except KeyboardInterrupt:
    print("\nStopped by user.")
    sys.exit(0)

在這個程式中,我們使用了一個 while 迴圈不斷地印出 “” 字元。每次印出 “” 之後,我們會暫停 0.1 秒,形成動畫效果。當使用者按下 Ctrl+C 時,程式會捕捉到 KeyboardInterrupt 例外,並印出 “Stopped by user.",然後終止程式。

內容解密:

在這個例子中,我們使用了 try-except 區塊來捕捉 ZeroDivisionError 和 KeyboardInterrupt 例外。這樣可以讓我們預防程式因為錯誤而終止執行,並提供更好的使用者經驗。

圖表翻譯:

這個流程圖顯示了程式的執行流程,從開始呼叫 spam 函式,到發生 ZeroDivisionError,然後印出錯誤訊息,最後繼續執行並結束。

ZigZag 程式碼解析

程式碼重寫與解說

首先,我們來看一下給定的程式碼,並進行必要的重寫和解說。原始程式碼如下:

import time, sys

indent = 0  # How many spaces to indent
indent_increasing = True  # Whether the indentation is increasing

try:
    while True:  # The main program loop
        print(' ' * indent, end='')
        print('********')
        time.sleep(0.1)  # Pause for 1/10th of a second
        if indent_increasing:
            # Increase the number of spaces:
            indent += 1
        else:
            # Decrease the number of spaces:
            indent -= 1
            if indent < 0:
                indent = 0  # Don't go below 0
                indent_increasing = True  # Switch direction
    # Switch direction when reaching maximum indentation
    if indent_increasing and indent >= 20:
        indent_increasing = False
except KeyboardInterrupt:
    sys.exit()

這個程式碼設計了一個簡單的動畫效果,印出一系列的 ********,並根據 indent 變數的值進行縮排。

內容解密:

讓我們一步一步地瞭解這個程式碼的作用:

  1. 初始化變數:我們初始化兩個變數,indent 用於控制縮排的空格數,indent_increasing 則用於控制縮排的方向。
  2. 主迴圈:程式碼進入一個無限迴圈 (while True),在每次迭代中,它會根據當前的 indent 值印出對應數量的空格後面跟著 ********
  3. 暫停:每次印出後,程式碼會暫停 0.1 秒,以創造動畫效果。
  4. 更新縮排:根據 indent_increasing 的值決定是否增加或減少縮排。如果 indent_increasingTrue,則增加縮排;否則,減少縮排。當縮排減少到 0 時,切換方向,開始增加縮排。
  5. 方向切換:當縮排達到一定值 (在這裡是 20) 時,切換方向,開始減少縮排。

圖表翻譯:

下面是一個簡單的 Plantuml 流程圖,用於描述這個程式碼的邏輯: 這個圖表展示了程式碼的主要流程,包括初始化、主迴圈、印出字串、暫停、更新縮排和檢查邊界等步驟。

程式碼分析:動態縮排的星號列印

首先,我們需要了解這段程式碼的功能。它是一個簡單的Python程式,負責印出一列星號( asterisks),並且在每次印出時,縮排的空格數量會根據某些條件進行增加或減少。

變數與初始設定

程式碼開始時,定義了兩個變數:indentindent_increasingindent用於記錄當前縮排的空格數量,初始值為0。indent_increasing是一個布林值,表示縮排是否正在增加,初始值為True

主要迴圈

程式碼的主要部分是一個無限迴圈(while True),這意味著程式將不斷執行直到被手動停止。迴圈內部包含了印出星號列的邏輯,以及根據indent_increasing的值調整縮排空格數量的邏輯。

印出星號列

在每次迴圈迭代中,程式碼使用print(' ' * indent, end='')印出相應數量的空格作為縮排,接著使用print('********')印出八個星號。time.sleep(0.1)引入了一個短暫的延遲,使得星號列的印出速度變得可控。

調整縮排

調整縮排的邏輯根據indent_increasing的值進行。如果indent_increasingTrue”,則增加indent的值。但是,一旦indent達到20,就切換indent_increasingFalse”,開始減少indent的值。這樣就形成了一個來回變化的縮排效果。

鍵盤中斷處理

程式碼還包含了一個try-except塊,用於捕捉鍵盤中斷(KeyboardInterrupt)異常。如果使用者按下Ctrl+C,程式碼就會捕捉到這個異常,並使用sys.exit()進行清晰的離開,而不是當機。

Plantuml 圖表:程式邏輯流程

圖表翻譯:

此Plantuml圖表描述了程式的邏輯流程。從開始到初始化變數,然後進入主迴圈。在主迴圈中,程式印出星號列,調整縮排,並檢查是否有鍵盤中斷。如果有,則離開程式;否則,繼續執行主迴圈。

內容解密:

這段程式碼示範瞭如何使用Python建立一個動態縮排的星號列印程式。透過調整indent_increasing的值和使用time.sleep(),我們可以控制星號列的印出速度和縮排效果。此外,程式碼還展示瞭如何使用try-except塊來捕捉鍵盤中斷異常,以確保程式的清晰離開。

增加縮排的空格數量

indent = indent + 1

indent 等於 20 時,將 indent_increasing 設為 False,表示縮排不再增加。

if indent == 20:
    indent_increasing = False

如果 indent_increasingFalse,則從 indent 中減去 1。一旦 indent 達到 0,縮排將再次增加。無論如何,程式執行都會跳回主程式迴圈的開始,以再次印出星號。

else:
    # 減少空格數量
    indent = indent - 1
    if indent == 0:
        indent_increasing = True

如果使用者在程式執行於 try 區塊內的任何時間點按下 CTRL-C,則此 except 陳述式會引發並處理 KeyboardInterrupt 例外:

except KeyboardInterrupt:
    sys.exit()

程式執行會移動到 except 區塊內,執行 sys.exit() 並離開程式。這樣,即使主程式迴圈是無限的,使用者仍然有一種方法可以關閉程式。

短程式:Spike

現在,讓我們建立另一個滾動動畫程式。這個程式使用字串複製和巢狀迴圈來繪製尖峰。開啟編輯器中的新檔案,輸入以下程式碼,並將其儲存為 spike.py

import time, sys
try:
    while True:  # 主程式迴圈
        # 繪製長度增加的線:
        for i in range(1, 9):
            print('-' * (i * i))
            time.sleep(0.1)
        
        # 繪製長度減少的線:
        for i in range(7, 1, -1):
            print('-' * (i * i))

內容解密:

上述程式碼使用兩個巢狀迴圈來印出長度增加和減少的線。外層的 while 迴圈確保程式會無限執行,直到使用者按下 CTRL-C。內層的 for 迴圈使用 range() 函式來產生從 1 到 8 的數字,並使用字串複製 ('-' * (i * i)) 來印出長度增加的線。接著,另一個 for 迴圈使用 range(7, 1, -1) 來產生從 7 到 2 的數字,並印出長度減少的線。 time.sleep(0.1) 函式用於在每次印出後暫停 0.1 秒,以創造動畫效果。

圖表翻譯:

此 Plantuml 圖表展示了程式的流程:從開始,繪製長度增加的線,然後繪製長度減少的線,暫停 0.1 秒,然後回到開始。

程式設計:繪製尖峰圖案

在這個程式設計中,我們將實作一個繪製尖峰圖案的程式。這個程式會不斷地繪製出不同大小的尖峰,直到使用者按下 CTRL-C 鍵。

程式碼

import time
import sys

try:
    # 繪製尖峰圖案
    while True:
        # 繪製尖峰的大小不斷增加
        for i in range(1, 10):
            print('-' * (i * i))
            time.sleep(0.1)
except KeyboardInterrupt:
    # 當使用者按下 CTRL-C 鍵時,程式結束
    sys.exit()

程式解釋

  1. 匯入模組:我們匯入了 timesys 模組,分別用於暫停程式執行和處理系統離開。
  2. try-except 區塊:我們使用 try-except 區塊來捕捉 KeyboardInterrupt 例外,這個例外會在使用者按下 CTRL-C 鍵時被引發。
  3. while 迴圈:我們使用 while 迴圈來不斷地繪製尖峰圖案。
  4. for 迴圈:在 while 迴圈內,我們使用 for 迴圈來繪製尖峰的大小不斷增加。變數 i 會從 1 開始,逐步增加到 9。
  5. print() 函式:我們使用 print() 函式來輸出尖峰圖案。'-' * (i * i) 會根據 i 的值輸出不同大小的尖峰。
  6. time.sleep() 函式:我們使用 time.sleep() 函式來暫停程式執行 0.1 秒,從而控制尖峰圖案的繪製速度。
  7. sys.exit() 函式:當使用者按下 CTRL-C 鍵時,程式會執行 sys.exit() 函式來結束程式。

執行結果

當你執行這個程式時,它會不斷地繪製出不同大小的尖峰,直到你按下 CTRL-C 鍵。尖峰圖案的大小會不斷增加,從 1 個 - 字元開始,逐步增加到 81 個 - 字元。

內容解密:

  • 我們使用 while 迴圈來不斷地繪製尖峰圖案。
  • 在 while 迴圈內,我們使用 for 迴圈來繪製尖峰的大小不斷增加。
  • 變數 i 會從 1 開始,逐步增加到 9。
  • 我們使用 print() 函式來輸出尖峰圖案。
  • '- ' * (i * i) 會根據 i 的值輸出不同大小的尖峰。

圖表翻譯:

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title Python 函式變數作用域與例外處理

package "Python 應用架構" {
    package "應用層" {
        component [主程式] as main
        component [模組/套件] as modules
        component [設定檔] as config
    }

    package "框架層" {
        component [Web 框架] as web
        component [ORM] as orm
        component [非同步處理] as async
    }

    package "資料層" {
        database [資料庫] as db
        component [快取] as cache
        component [檔案系統] as fs
    }
}

main --> modules : 匯入模組
main --> config : 載入設定
modules --> web : HTTP 處理
web --> orm : 資料操作
orm --> db : 持久化
web --> cache : 快取查詢
web --> async : 背景任務
async --> fs : 檔案處理

note right of web
  Flask / FastAPI / Django
end note

@enduml

這個流程圖展示了程式的執行流程。當程式開始執行時,它會進入 while 迴圈,不斷地繪製尖峰圖案。每次繪製完畢後,程式會暫停 0.1 秒。然後,它會檢查是否按下了 CTRL-C 鍵。如果按下了,程式會結束;否則,它會繼續繪製尖峰圖案。

函式的優勢

函式是將程式碼組織成邏輯群組的主要方式。由於函式中的變數存在於自己的區域性範圍內,一個函式中的程式碼不能直接影響其他函式中區域性變數的值。這限制了可以修改變數值的程式碼段,從而在除錯時提供了幫助。

函式的定義和呼叫

函式的程式碼在函式被呼叫時執行,而不是在函式被定義時。定義一個函式時,僅僅是建立了這個函式的結構和內容,但並不會立即執行其中的程式碼。直到函式被呼叫時,程式碼才會開始執行。

函式與函式呼叫的區別

函式是一段可以多次呼叫的程式碼塊,它定義了一些動作或計算。函式呼叫則是指在程式中某個位置執行這個函式的動作。當一個函式被呼叫時,程式會轉到函式的定義位置並執行其中的程式碼。

全域範圍和區域性範圍

在Python程式中,只有一個全域範圍(global scope)。區域性範圍(local scope)則可能有多個,每個函式都有一個自己的區域性範圍。當一個函式被定義時,就會建立一個新的區域性範圍,這個範圍只對這個函式內的變數和程式碼有效。

從技術架構視角來看,Python 的函式作用域機制,對於程式碼的組織和維護至關重要。深入剖析區域性變數和全域變數的區別,可以發現,有效利用函式的區域性作用域特性,能大幅降低程式碼的複雜度,並提升程式碼的可讀性和可維護性。透過將程式碼邏輯封裝在函式內,可以有效避免變數命名衝突,並提高程式碼的模組化程度。同時,善用 try-except 區塊捕捉和處理異常,能提升程式的穩定性和容錯能力,避免程式因未預期的錯誤而當機。然而,過度使用全域變數則可能導致程式碼難以理解和除錯,因此,應盡量避免在函式外部修改全域變數的值。展望未來,隨著程式碼規模的日益增長,模組化設計和錯誤處理機制的重要性將更加凸顯。對於追求程式碼品質的開發者而言,深入理解並善用函式作用域和例外處理機制,將是提升程式碼健壯性和可維護性的關鍵。玄貓認為,掌握這些核心概念,將有助於開發者寫出更清晰、更穩定的 Python 程式碼。