Python 變數作用域:區域變數、全域變數
在 Python 程式設計中,變數 (variables) 是儲存數據的基本容器。然而,並非所有變數在程式的任何地方都能被存取。變數的「作用域」(scope) 定義了程式碼中可以存取該變數的區域。正確理解變數作用域對於編寫清晰、可維護且無錯誤的程式碼至關重要。
本文將深入探討 Python 中的兩種主要變數作用域:區域變數 (local variables) 和全域變數 (global variables),並介紹如何使用 locals()
和 globals()
函數來檢視這些作用域。透過簡單的範例,你將學習它們的差異、如何使用它們,以及一些重要的相關關鍵字。
🌍 什麼是全域變數 (Global Variables)?
全域變數是在所有函數之外定義的變數。這意味著它們的作用域是整個程式檔案。
主要特點:
- 定義位置: 在 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
在這個例子中,greeting
和 version
都是全域變數。print_greeting
函數可以讀取它們的值。如果在函數內部嘗試修改全域變數(而未使用特殊關鍵字),Python 會將其視為一個新的區域變數,這點稍後會討論。
🏠 什麼是區域變數 (Local Variables)?
區域變數是在函數內部定義的變數。它們的作用域僅限於定義它們的函數。
主要特點:
- 定義位置: 在函數定義的內部。
- 存取範圍: 只能在定義它的函數內部存取。一旦函數執行完畢,區域變數通常就會被銷毀。
- 生命週期: 當函數被呼叫時創建,當函數返回或結束時銷毀。
範例:
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
。這是因為 message
、local_value
和 param
(函數參數也是區域變數) 都是 my_function
的區域變數,在函數外部是不可見的。
👀 檢視作用域:globals()
與 locals()
函數
Python 提供了兩個內建函數 globals()
和 locals()
,它們可以幫助我們檢視目前作用域中的變數。這兩個函數都返回一個字典。
globals()
函數
globals()
函數返回一個代表目前全域作用域的字典。這個字典包含了所有全域變數的名稱作為鍵 (key),以及它們對應的值 (value)。
範例:
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()
相同,返回全域作用域的字典。
範例:
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:
- 區域 (Local) 作用域: Python 首先在函數的區域作用域中尋找變數。
- 全域 (Global) 作用域: 如果在區域作用域中找不到,Python 會在外層的全域作用域中尋找。
如果一個區域變數與一個全域變數同名,該區域變數會在函數內部「遮蔽」(shadow) 全域變數。
範例:
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 該變數是全域變數。
範例:
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
關鍵字。
範例:
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
test_nonlocal
中的money
初始為 100,並在reset_account
被呼叫前印出。reset_account
內部使用nonlocal money
指明要修改外層的money
,並將其設為 0,然後印出。- 由於外層的
money
已被修改,test_nonlocal
最後印出money
時顯示為 0。
💡 何時使用區域變數 vs. 全域變數
選擇使用區域變數還是全域變數取決於具體需求,但有一些通用指引:
區域變數 (Local Variables):
- 優點:
- 封裝性 (Encapsulation): 變數的作用域被限制在函數內,使得函數更獨立,易於理解和測試。
- 減少副作用 (Side Effects): 修改區域變數不會影響函數外部的程式碼。
- 避免命名衝突 (Naming Conflicts): 不同函數可以使用相同的變數名而不會互相干擾。
- 缺點:
- 作用域有限(這是設計特性,通常是好事)。
- 建議: 盡可能優先使用區域變數。 這是良好程式設計實踐的基礎,有助於編寫模組化和可維護的程式碼。
全域變數 (Global Variables):
- 優點:
- 共享狀態: 對於需要在程式多個部分共享的數據(例如,設定值、常數)可能有用。
- 缺點:
- 可讀性和維護性差: 很難追蹤全域變數在何處被讀取或修改,可能導致「義大利麵條式程式碼」(spaghetti code)。
- 增加命名衝突的風險: 尤其在大型專案中。
- 測試困難: 依賴全域狀態的函數更難獨立測試。
- 建議: 謹慎使用全域變數。
- 適用情況:
- 定義常數 (constants),例如
PI = 3.14159
或MAX_CONNECTIONS = 10
。按照慣例,常數名稱通常使用全大寫。 - 程式級別的設定值,這些設定值在程式啟動時設定且很少改變。
- 定義常數 (constants),例如
- 替代方案: 通常可以透過函數參數傳遞數據,或將相關數據和行為封裝在類別 (class) 中,以避免使用全域變數。
- 適用情況:
📝 總結
理解 Python 中的區域變數和全域變數及其作用域是掌握 Python 程式設計的關鍵一步。
- 全域變數 在整個程式中都可存取,但在函數內修改時需要
global
關鍵字。 - 區域變數 僅在其定義的函數內部有效,有助於編寫更模組化和安全的程式碼。
globals()
和locals()
函數可以幫助檢視目前作用域中的變數集合。- 當名稱衝突時,區域變數會遮蔽全域變數。
nonlocal
關鍵字用於在嵌套函數中修改外層函數的變數。
明智地使用變數作用域可以讓你的 Python 程式碼更清晰、更易於管理,並減少潛在的錯誤。多加練習,你就能更熟練地運用這些概念!