Skip to content

Python 變數作用域:區域變數、全域變數

在 Python 程式設計中,變數 (variables) 是儲存數據的基本容器。然而,並非所有變數在程式的任何地方都能被存取。變數的「作用域」(scope) 定義了程式碼中可以存取該變數的區域。正確理解變數作用域對於編寫清晰、可維護且無錯誤的程式碼至關重要。

本文將深入探討 Python 中的兩種主要變數作用域:區域變數 (local variables) 和全域變數 (global variables),並介紹如何使用 locals()globals() 函數來檢視這些作用域。透過簡單的範例,你將學習它們的差異、如何使用它們,以及一些重要的相關關鍵字。

🌍 什麼是全域變數 (Global Variables)?

全域變數是在所有函數之外定義的變數。這意味著它們的作用域是整個程式檔案。

主要特點:

  • 定義位置: 在 Python 程式碼的頂層,不屬於任何函數或類別。
  • 存取範圍: 程式中的任何地方都可以存取全域變數,包括函數內部和外部。

範例:

python
# 這是一個全域變數
greeting = "Hello from global scope!"
version = 1.0

def print_greeting():
    # 函數內部可以存取全域變數
    print(f"Inside function: {greeting}, Version: {version}")

# 呼叫函數
print_greeting()

# 函數外部也可以存取全域變數
print(f"Outside function: {greeting}, Version: {version}")

輸出:

Inside function: Hello from global scope!, Version: 1.0
Outside function: Hello from global scope!, Version: 1.0

在這個例子中,greetingversion 都是全域變數。print_greeting 函數可以讀取它們的值。如果在函數內部嘗試修改全域變數(而未使用特殊關鍵字),Python 會將其視為一個新的區域變數,這點稍後會討論。

🏠 什麼是區域變數 (Local Variables)?

區域變數是在函數內部定義的變數。它們的作用域僅限於定義它們的函數。

主要特點:

  • 定義位置: 在函數定義的內部。
  • 存取範圍: 只能在定義它的函數內部存取。一旦函數執行完畢,區域變數通常就會被銷毀。
  • 生命週期: 當函數被呼叫時創建,當函數返回或結束時銷毀。

範例:

python
def my_function(param):
    # 這是一個區域變數
    message = "Hello from local scope!"
    local_value = 100 + param
    print(f"Inside function: {message}, Value: {local_value}")

# 呼叫函數
my_function(50)

# 嘗試在函數外部存取區域變數將會導致錯誤
# print(f"Outside function: {message}") # 這行會產生 NameError
# print(f"Outside function: {local_value}") # 這行也會產生 NameError
# print(f"Outside function: {param}") # 這行也會產生 NameError (param 也是 my_function 的區域變數)

輸出:

Inside function: Hello from local scope!, Value: 150

如果你取消註解最後幾行並執行程式碼,你會遇到 NameError。這是因為 messagelocal_valueparam (函數參數也是區域變數) 都是 my_function 的區域變數,在函數外部是不可見的。

👀 檢視作用域:globals()locals() 函數

Python 提供了兩個內建函數 globals()locals(),它們可以幫助我們檢視目前作用域中的變數。這兩個函數都返回一個字典。

globals() 函數

globals() 函數返回一個代表目前全域作用域的字典。這個字典包含了所有全域變數的名稱作為鍵 (key),以及它們對應的值 (value)。

範例:

python
global_var_x = 10
global_var_y = "Python"

def show_globals():
    print("--- Inside function ---")
    # 即使在函數內呼叫,globals() 仍然顯示全域作用域
    current_globals = globals()
    print(f"Accessing global_var_x via globals(): {current_globals.get('global_var_x')}")
    # 你也可以直接存取全域變數
    print(f"Directly accessing global_var_x: {global_var_x}")

print("--- Outside function ---")
all_globals = globals()
print(f"global_var_x in global scope: {all_globals.get('global_var_x')}")
print(f"global_var_y in global scope: {all_globals.get('global_var_y')}")
# globals() 的內容會很多,通常包含許多內建名稱
# print(all_globals) # 取消註解以查看所有全域變數

show_globals()

輸出 (部分內容,實際輸出會更多):

--- Outside function ---
global_var_x in global scope: 10
global_var_y in global scope: Python
--- Inside function ---
Accessing global_var_x via globals(): 10
Directly accessing global_var_x: 10

globals() 的輸出通常包含很多 Python 內建的名稱 (如 __name__, __doc__ 等)。

locals() 函數

locals() 函數返回一個代表目前區域作用域的字典。

  • 在函數內部呼叫時: 它返回該函數的區域變數和參數。
  • 在模組層級 (即任何函數之外) 呼叫時: locals() 的行為與 globals() 相同,返回全域作用域的字典。

範例:

python
global_value = "I am global"

def scope_test(arg1, arg2="default"):
    local_var1 = 100
    local_var2 = "local string"
    print("--- Inside scope_test function ---")
    current_locals = locals()
    print("locals() output:")
    for key, value in current_locals.items():
        print(f"  {key}: {value}")
    # print(local_var1) # 可以直接存取

scope_test(5, arg2="custom")
print("\n--- At module level (outside any function) ---")
module_level_locals = locals()
module_level_globals = globals()

# 在模組層級,locals() 和 globals() 返回相同的字典
print(f"Is module_level_locals the same object as module_level_globals? {module_level_locals is module_level_globals}")
# print(f"global_value in module_level_locals: {module_level_locals.get('global_value')}")

輸出:

--- Inside scope_test function ---
locals() output:
  arg1: 5
  arg2: custom
  local_var1: 100
  local_var2: local string

--- At module level (outside any function) ---
Is module_level_locals the same object as module_level_globals? True

重要注意事項: 雖然 locals() 返回一個字典,並且你可以修改這個字典,但 Python 文件指出,對 locals() 返回的字典所做的更改可能不會影響解釋器使用的區域變數的值。因此,最好將 locals() 主要用於檢視區域變數,而不是修改它們。

🔍 變數名稱相同時會發生什麼?(Name Collision)

當區域變數和全域變數具有相同的名稱時,Python 如何決定使用哪一個?Python 有一個查找變數的順序,通常稱為 LEGB 規則 (Local, Enclosing function locals, Global, Built-in)。對於這個討論,主要關注 Local 和 Global:

  1. 區域 (Local) 作用域: Python 首先在函數的區域作用域中尋找變數。
  2. 全域 (Global) 作用域: 如果在區域作用域中找不到,Python 會在外層的全域作用域中尋找。

如果一個區域變數與一個全域變數同名,該區域變數會在函數內部「遮蔽」(shadow) 全域變數。

範例:

python
x = "I am global"

def demonstrate_shadowing():
    x = "I am local"  # 這個 x 是區域變數,遮蔽了全域的 x
    print(f"Inside function (accessing x): {x}")
    print(f"Inside function (locals()): {locals().get('x')}")
    print(f"Inside function (globals()): {globals().get('x')}")


demonstrate_shadowing()
print(f"Outside function (accessing x): {x}")

輸出:

Inside function (accessing x): I am local
Inside function (locals()): I am local
Inside function (globals()): I am global
Outside function (accessing x): I am global

demonstrate_shadowing 函數內部,當你直接存取 x 時,它指的是區域變數。locals()['x'] 也會顯示區域 x。然而,globals()['x'] 仍然可以存取到全域的 x。函數外部的 x 仍然是全域變數。

🔑 global 關鍵字:在函數內修改全域變數

預設情況下,如果在函數內部對一個與全域變數同名的變數賦值,Python 會創建一個新的區域變數。如果你想在函數內部修改一個已經存在的全域變數的值,你需要使用 global 關鍵字。

問題: 如何在函數內部修改全域變數? 解決方案: 使用 global 關鍵字明確告知 Python 該變數是全域變數。

範例:

python
counter = 0  # 全域變數

def increment_counter():
    global counter  # 宣告 counter 是全域變數
    counter += 1
    print(f"Counter inside function: {counter}")

increment_counter()
increment_counter()
print(f"Counter outside function: {counter}")

輸出:

Counter inside function: 1
Counter inside function: 2
Counter outside function: 2

increment_counter 函數中,global counter 告訴 Python counter 應該引用全域作用域中的 counter 變數,而不是創建一個新的區域變數。因此,每次呼叫函數時,全域的 counter 都會被修改。

注意事項: 過度使用 global 關鍵字可能會使程式碼難以追蹤和除錯,因為變數的值可以在程式的許多不同地方被修改。通常建議盡量減少對全域變數的修改。

🔗 nonlocal 關鍵字:修改嵌套函數中的變數

Python 允許函數內部定義函數,這稱為嵌套函數 (nested functions)。有時,你可能需要從內部函數修改外部(但非全域)函數的變數。這時,可以使用 nonlocal 關鍵字。

問題: 如何在嵌套函數中修改其外層函數(非全域)的變數? 解決方案: 使用 nonlocal 關鍵字。

範例:

python
def test_nonlocal():
    money = 100  # 外部函數中的整數變數
    def reset_account():
        nonlocal money  # 使用外層函數的 money 變數
        money = 0       # 修改該變數
        print(f"Reset money: {money}")

    print(f"Before reset: x = {money}")
    reset_account()     # 調用內部函數
    print(f"After reset: x = {money}")

test_nonlocal()

輸出:

Before reset: x = 100
Inside inner: x = 0
After reset: x = 0
  1. test_nonlocal 中的 money 初始為 100,並在 reset_account 被呼叫前印出。
  2. reset_account 內部使用 nonlocal money 指明要修改外層的 money,並將其設為 0,然後印出。
  3. 由於外層的 money 已被修改,test_nonlocal 最後印出 money 時顯示為 0。

💡 何時使用區域變數 vs. 全域變數

選擇使用區域變數還是全域變數取決於具體需求,但有一些通用指引:

區域變數 (Local Variables):

  • 優點:
    • 封裝性 (Encapsulation): 變數的作用域被限制在函數內,使得函數更獨立,易於理解和測試。
    • 減少副作用 (Side Effects): 修改區域變數不會影響函數外部的程式碼。
    • 避免命名衝突 (Naming Conflicts): 不同函數可以使用相同的變數名而不會互相干擾。
  • 缺點:
    • 作用域有限(這是設計特性,通常是好事)。
  • 建議: 盡可能優先使用區域變數。 這是良好程式設計實踐的基礎,有助於編寫模組化和可維護的程式碼。

全域變數 (Global Variables):

  • 優點:
    • 共享狀態: 對於需要在程式多個部分共享的數據(例如,設定值、常數)可能有用。
  • 缺點:
    • 可讀性和維護性差: 很難追蹤全域變數在何處被讀取或修改,可能導致「義大利麵條式程式碼」(spaghetti code)。
    • 增加命名衝突的風險: 尤其在大型專案中。
    • 測試困難: 依賴全域狀態的函數更難獨立測試。
  • 建議: 謹慎使用全域變數。
    • 適用情況:
      • 定義常數 (constants),例如 PI = 3.14159MAX_CONNECTIONS = 10。按照慣例,常數名稱通常使用全大寫。
      • 程式級別的設定值,這些設定值在程式啟動時設定且很少改變。
    • 替代方案: 通常可以透過函數參數傳遞數據,或將相關數據和行為封裝在類別 (class) 中,以避免使用全域變數。

📝 總結

理解 Python 中的區域變數和全域變數及其作用域是掌握 Python 程式設計的關鍵一步。

  • 全域變數 在整個程式中都可存取,但在函數內修改時需要 global 關鍵字。
  • 區域變數 僅在其定義的函數內部有效,有助於編寫更模組化和安全的程式碼。
  • globals()locals() 函數可以幫助檢視目前作用域中的變數集合。
  • 當名稱衝突時,區域變數會遮蔽全域變數。
  • nonlocal 關鍵字用於在嵌套函數中修改外層函數的變數。

明智地使用變數作用域可以讓你的 Python 程式碼更清晰、更易於管理,並減少潛在的錯誤。多加練習,你就能更熟練地運用這些概念!

📚 參考資料

KF Software House