所有文章

Python 變數作用域:global vs nonlocal 完整圖解

2,9698 分鐘閱讀
python變數作用域初學者

你有沒有遇過這種情況:在函式裡面明明改了變數的值,結果外面的變數卻紋風不動?或者加了一個 global,結果改到了不該改的東西?

這篇文章會帶你從零開始,搞懂 Python 變數作用域的核心機制——LEGB 規則,以及 globalnonlocal 這兩個關鍵字到底在做什麼。文末還有互動式演示和隨堂測驗,幫你徹底內化這些觀念。

什麼是 LEGB 規則?

Python 查找變數時,會按照固定的順序搜尋四個「作用域(Scope)」,這就是所謂的 LEGB 規則

  1. L — Local(區域):目前函式內部定義的變數
  1. E — Enclosing(外層函式):包住當前函式的外層函式中的變數(巢狀函式才有)
  1. G — Global(全域):模組(檔案)層級的變數
  1. B — Built-in(內建):Python 內建的名稱,例如 printlen

Python 會從 L 開始,一路往外找到 B。找到的第一個就用,找不到就報 NameError

python
x = "全域"        # G — Global

def outer():
    x = "外層"    # E — Enclosing

    def inner():
        x = "區域"  # L — Local
        print(x)   # 先找 L → 找到了!印出 "區域"

    inner()

outer()

賦值的陷阱:為什麼改不到外面的變數?

Python 有一條重要的規則:在函式內部對變數賦值,Python 會自動將它視為區域變數,不管外面有沒有同名的變數。

python
count = 0

def increment():
    count = count + 1  # ❌ UnboundLocalError!
    # Python 看到 count = ... 就認定 count 是區域變數
    # 但右邊的 count 還沒被賦值,所以報錯

increment()

這就是為什麼我們需要 globalnonlocal 來「宣告」我們要修改的是哪一層的變數。

global 關鍵字

global 告訴 Python:「這個變數不是區域的,請直接去全域(模組層級)找。」

python
count = 0

def increment():
    global count      # 宣告:我要用全域的 count
    count = count + 1 # ✅ 現在可以修改了

increment()
increment()
print(count)  # 輸出 2
⚠️
注意:過度使用 global 會讓程式碼難以維護和除錯。在大型專案中,盡量避免使用全域變數,改用參數傳遞或類別封裝。

nonlocal關鍵字

nonlocal 告訴 Python:「這個變數不是區域的,請去上一層的外層函式找。」它只能用在巢狀函式中。

python
def make_counter():
    count = 0  # 外層函式的變數

    def increment():
        nonlocal count  # 宣告:我要用外層的 count
        count += 1
        return count

    return increment

counter = make_counter()
print(counter())  # 1
print(counter())  # 2
print(counter())  # 3

這是 nonlocal 最經典的應用場景——閉包(Closure)。外層函式的變數在函式結束後不會消失,而是被內層函式「記住」了。

global vs nonlocal:到底選哪個?

📊
比較表: 目標作用域:global → 模組層級(最外層)|nonlocal → 上一層外層函式 是否需要巢狀函式:global → 不需要|nonlocal → 需要 典型用途:global → 全域設定、單例模式|nonlocal → 閉包、計數器、裝飾器 推薦程度:global → 盡量少用|nonlocal → 適當使用

實戰範例:裝飾器中的 nonlocal

nonlocal 在裝飾器中非常實用。以下是一個「呼叫計數器」的範例:

python
def call_counter(func):
    """記錄函式被呼叫了幾次"""
    count = 0

    def wrapper(*args, **kwargs):
        nonlocal count
        count += 1
        print(f"{func.__name__} 已被呼叫 {count} 次")
        return func(*args, **kwargs)

    return wrapper

@call_counter
def greet(name):
    print(f"你好,{name}!")

greet("小明")  # greet 已被呼叫 1 次 → 你好,小明!
greet("小華")  # greet 已被呼叫 2 次 → 你好,小華!

常見錯誤與陷阱

陷阱 1:忘記宣告就修改

python
total = 0

def add(n):
    total += n  # ❌ UnboundLocalError
    # 解法:加上 global total

陷阱 2:global 穿透多層函式

python
x = "全域"

def outer():
    x = "外層"

    def inner():
        global x    # 跳過外層,直接改全域!
        x = "內層"

    inner()
    print(x)  # 印出 "外層"(沒被改到)

outer()
print(x)      # 印出 "內層"(全域被改了)

如果你的意圖是修改 outer 裡的 x,應該用 nonlocal 而不是 global

陷阱 3:nonlocal 不能用在最外層

python
x = 10

def my_func():
    nonlocal x  # ❌ SyntaxError: no binding for nonlocal 'x' found
    # nonlocal 只能用在巢狀函式中,不能指向全域變數

互動練習

理論看完了,現在動手試試看吧!在「學習模式」中點選不同的按鈕,觀察變數如何變化;在「挑戰模式」中測試你的理解。

互動演示:global vs nonlocal

透過視覺化互動,理解變數在不同層級中的覆寫與修改行為。

互動演示區

Global Scope (全域)最外層檔案層級
x = 🍎 全域蘋果 (Global)
Enclosing Scope (外層函式)def outer_func():
x = 🍊 外層橘子 (Enclosing)
Local Scope (內層函式)def inner_func():
x = 未定義 (或獨立變數)

要在內層函式修改 x,請選擇一種方式:

原理解析

請選擇一個操作

Python 的作用域就像俄羅斯娃娃。最外層是「全域 (Global)」,中間是「外層函式 (Enclosing)」,最裡面是「內層函式 (Local)」。

scope.py
# 初始狀態
x = "🍎" # 全域變數
 
def outer_func():
x = "🍊" # 外層變數
 
def inner_func():
# 等待你的操作...
pass

總結

  • Python 用 LEGB 規則查找變數:Local → Enclosing → Global → Built-in
  • 在函式中賦值會自動建立區域變數,不會修改外層
  • global 讓你修改模組層級的變數(盡量少用)
  • nonlocal 讓你修改外層函式的變數(閉包、裝飾器常用)
  • 遇到 UnboundLocalError 時,先檢查是不是忘了加 globalnonlocal
💡
最佳實踐:優先使用參數傳遞和回傳值來共享資料,而不是依賴 global。當你真的需要「狀態記憶」時,考慮使用類別或 nonlocal 搭配閉包。

版權聲明

文章標題:Python 變數作用域:global vs nonlocal 完整圖解

文章作者:阿盧老師

文章連結:https://codinglu.tw/blog/python-nonlocal-global

授權條款:本文採用 CC BY-NC 4.0 授權。轉載請標明出處。