本文 AI 產出,尚未審核

Python 函式作用域(local / global / nonlocal)


簡介

在 Python 中,函式(function)是程式結構的核心,而變數的可見範圍(scope)直接影響程式的可讀性、可維護性與執行結果。
了解 localglobalnonlocal 三種作用域的差異,能讓你在設計 API、撰寫迴圈或是實作閉包(closure)時,避免常見的錯誤與不可預期的行為。

本篇文章以 淺顯易懂 的語言說明作用域概念,搭配 實用範例,從基礎的局部變數到進階的 nonlocal 用法,協助初學者快速上手,同時提供給中階開發者作為最佳實踐的參考。


核心概念

1. 局部作用域(Local Scope)

函式內部宣告的變數預設屬於 局部作用域,只在該函式執行期間有效。函式結束後,局部變數會被回收,外部無法直接存取。

def add(a, b):
    result = a + b          # result 為局部變數
    return result

total = add(3, 5)            # total = 8
# print(result)  # NameError: name 'result' is not defined
  • 特點
    • 只在函式內部可見。
    • 允許與外部同名變數共存,互不干擾。

2. 全域作用域(Global Scope)

在模組最外層(不在任何函式或類別內)宣告的變數屬於 全域作用域,整個模組內都可以直接存取。若在函式內需要 修改 全域變數,必須使用 global 關鍵字告訴 Python 這個名稱指向全域空間。

counter = 0                     # 全域變數

def increase():
    global counter              # 宣告要使用全域的 counter
    counter += 1                # 直接修改全域變數

increase()
print(counter)   # 1
  • 注意:未使用 global 而直接賦值,Python 會把變數視為 局部,導致 UnboundLocalError

3. 非局部作用域(Nonlocal Scope)

nonlocal 用於 嵌套函式(function inside another function),讓內層函式可以 讀取或修改 外層(但不是全域)函式的變數。這是實作 閉包 時常用的手法。

def outer():
    total = 0                   # outer 的局部變數

    def inner(x):
        nonlocal total          # 取得 outer 中的 total
        total += x
        return total

    return inner

acc = outer()
print(acc(5))   # 5
print(acc(3))   # 8
  • 關鍵點
    • nonlocal 只能作用於最近一層的封閉作用域。
    • 若變數在全域層級,仍須使用 global,不能用 nonlocal

4. 變數遮蔽(Variable Shadowing)

同名變數在不同作用域會互相 遮蔽,即內層的名稱會覆蓋外層的名稱。這在閱讀程式碼時容易產生混淆,應盡量避免。

value = 10   # 全域

def func():
    value = 5     # 局部,遮蔽全域的 value
    print(value) # 5

func()
print(value)     # 10

5. 可變物件與不可變物件的作用域行為

  • 不可變物件(int、float、str、tuple 等)在局部賦值時會重新綁定名稱,不會影響外層變數。
  • 可變物件(list、dict、set 等)則可在局部直接 修改,外層參照會同步改變。
def modify():
    lst = [1, 2, 3]   # 局部變數,指向新的 list
    lst.append(4)     # 修改的是同一個物件
    return lst

print(modify())   # [1, 2, 3, 4]

如果想在函式內改變外層的可變物件,仍不需要 globalnonlocal,只要直接操作物件本身即可。


常見陷阱與最佳實踐

陷阱 說明 解決方式
未使用 global 而賦值 會產生 UnboundLocalError,因為 Python 認為是局部變數但未初始化。 在函式開頭加 global var_name,或改用參數傳遞。
nonlocal 用錯層級 試圖在最外層函式使用 nonlocal 會觸發 SyntaxError 只在 嵌套函式 中使用,或改為 global
可變預設參數 def f(arg=[]): 會在函式定義時就建立同一個 list,導致跨呼叫共享狀態。 使用 None 作為預設值,再在函式內部初始化:if arg is None: arg = []
過度使用全域變數 使程式耦合度提升,難以測試與除錯。 儘量把資料作為參數傳遞,或使用類別封裝狀態。
名稱遮蔽 同名變數在不同作用域混淆,增加閱讀難度。 使用具描述性的名稱,例如 global_counterinner_total

最佳實踐

  1. 最小化全域變數:僅保留真正需要跨模組共享的常數或設定。
  2. 明確使用 global / nonlocal:在程式碼第一行就寫出,提升可讀性。
  3. 避免可變預設參數:永遠使用 None 作為預設值。
  4. 利用閉包封裝狀態:在需要保持「私有」狀態時,使用 nonlocal 建立閉包,代替全域變數。
  5. 寫單元測試:作用域錯誤往往在執行時才顯現,測試能提前捕捉。

實際應用場景

1. 計數器(Counter)

在不使用全域變數的情況下,透過 nonlocal 建立一個 閉包計數器,每次呼叫都返回遞增的值:

def make_counter(start=0):
    count = start
    def next_number():
        nonlocal count
        count += 1
        return count
    return next_number

counter = make_counter(10)
print(counter())   # 11
print(counter())   # 12

此技巧常見於 產生唯一 ID序列產生器 等需求。

2. 配置檔(Config)共享

在大型專案中,常把配置信息放在模組的全域變數,並在需要修改時使用 global

# config.py
DEBUG = False
MAX_CONNECTION = 10

# app.py
import config

def enable_debug():
    global DEBUG          # 這裡其實不需要 global,直接修改模組屬性即可
    config.DEBUG = True

enable_debug()
print(config.DEBUG)  # True

3. 裝飾器(Decorator)實作

裝飾器本質上是 函式返回函式,常利用 nonlocal 保存原始函式的參數或狀態:

def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            nonlocal times
            result = None
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")   # 會印出三次

4. 多執行緒共享狀態

在多執行緒環境下,使用 全域變數 + thread lock 來保證安全:

import threading

total_requests = 0
lock = threading.Lock()

def handle_request():
    global total_requests
    with lock:
        total_requests += 1
    # 處理其他邏輯...

threads = [threading.Thread(target=handle_request) for _ in range(100)]
for t in threads: t.start()
for t in threads: t.join()
print(total_requests)   # 100

總結

  • 局部作用域(local)是函式最基本的變數範圍,保證變數不會意外洩漏。
  • 全域作用域(global)適合放置跨模組共享的常數或設定,但修改時必須使用 global 關鍵字。
  • 非局部作用域(nonlocal)是 Python 針對 嵌套函式 提供的特殊機制,讓內層函式能操作外層函式的變數,常用於閉包與裝飾器。
  • 正確掌握這三種作用域,不僅能避免 UnboundLocalErrorNameError 等常見錯誤,還能寫出 可讀、可維護、可測試 的程式碼。

透過本文的範例與最佳實踐,你現在應該能在日常開發中自信地運用 globalnonlocal,以及設計出更清晰的函式介面。祝你在 Python 的世界裡寫出更好、更穩定的程式!