Python 變數作用域 (Scope)

變數作用域決定了變數在程式碼中可以被存取的範圍。Python 有四種作用域:Local、Enclosing、Global、Built-in(LEGB 規則)。

LEGB 規則

Python 查找變數時,會按照以下順序:

  1. Local(區域):函數內部定義的變數
  2. Enclosing(外層函數):外層函數的區域變數(用於巢狀函數)
  3. Global(全域):模組層級定義的變數
  4. Built-in(內建):Python 內建的名稱(如 printlen
x = "global"  # Global

def outer():
    x = "enclosing"  # Enclosing
    
    def inner():
        x = "local"  # Local
        print(x)  # local
    
    inner()
    print(x)  # enclosing

outer()
print(x)  # global

Local Scope(區域作用域)

在函數內部定義的變數只在該函數內有效:

def my_function():
    x = 10  # 區域變數
    print(x)

my_function()  # 10
# print(x)  # NameError: name 'x' is not defined

每次呼叫函數,區域變數都會重新建立:

def counter():
    count = 0
    count += 1
    return count

print(counter())  # 1
print(counter())  # 1(每次都是新的 count)

Global Scope(全域作用域)

在所有函數之外定義的變數是全域變數,可以在任何地方讀取:

name = "Alice"  # 全域變數

def greet():
    print(f"Hello, {name}!")  # 可以讀取全域變數

greet()  # Hello, Alice!

修改全域變數

在函數內要修改全域變數,必須使用 global 關鍵字:

count = 0

def increment():
    global count  # 聲明使用全域變數
    count += 1

increment()
increment()
print(count)  # 2

如果不使用 global,會建立一個同名的區域變數:

count = 0

def increment():
    count = 100  # 這是新的區域變數,不是全域的 count
    print(f"Inside: {count}")

increment()  # Inside: 100
print(f"Outside: {count}")  # Outside: 0
過度使用全域變數會讓程式難以維護和除錯。盡量使用參數傳遞和回傳值。

Enclosing Scope(外層函數作用域)

巢狀函數可以存取外層函數的變數:

def outer():
    message = "Hello"  # 外層函數的變數
    
    def inner():
        print(message)  # 可以讀取外層變數
    
    inner()

outer()  # Hello

修改外層變數

要修改外層函數的變數,使用 nonlocal 關鍵字:

def outer():
    count = 0
    
    def inner():
        nonlocal count  # 聲明使用外層變數
        count += 1
        print(f"Count: {count}")
    
    inner()  # Count: 1
    inner()  # Count: 2
    inner()  # Count: 3

outer()

閉包 (Closure)

函數可以「記住」它被定義時的外層變數:

def create_counter():
    count = 0
    
    def counter():
        nonlocal count
        count += 1
        return count
    
    return counter

counter1 = create_counter()
counter2 = create_counter()

print(counter1())  # 1
print(counter1())  # 2
print(counter1())  # 3
print(counter2())  # 1(獨立的計數器)

Built-in Scope(內建作用域)

Python 內建的函數和名稱,如 printlenrange 等:

# 可以覆蓋內建名稱(但不建議)
print = "Hello"
# print("test")  # TypeError!

# 恢復內建函數
del print
print("test")  # test

不要用內建函數的名稱作為變數名稱:

# 不好的做法
list = [1, 2, 3]  # 覆蓋了內建的 list
dict = {"a": 1}   # 覆蓋了內建的 dict
str = "hello"     # 覆蓋了內建的 str

查看作用域中的變數

x = "global"

def my_function():
    y = "local"
    print("Local:", locals())   # 區域變數
    print("Global:", globals()) # 全域變數

my_function()

實際範例

設定選項

# 全域設定(謹慎使用)
DEBUG = False

def set_debug(value):
    global DEBUG
    DEBUG = value

def log(message):
    if DEBUG:
        print(f"[DEBUG] {message}")

log("Test")          # 不輸出
set_debug(True)
log("Test")          # [DEBUG] Test

建立工廠函數

def create_multiplier(factor):
    """建立一個乘法函數"""
    def multiplier(number):
        return number * factor  # 記住外層的 factor
    return multiplier

double = create_multiplier(2)
triple = create_multiplier(3)

print(double(5))  # 10
print(triple(5))  # 15

快取裝飾器

def cache(func):
    """簡單的快取裝飾器"""
    cached_results = {}  # 外層變數
    
    def wrapper(*args):
        if args not in cached_results:
            cached_results[args] = func(*args)
        return cached_results[args]
    
    return wrapper

@cache
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(100))  # 很快就能算出來

最佳實踐

  1. 盡量避免全域變數:使用參數傳遞和回傳值
  2. 縮小作用域:變數應該在最小的必要範圍內定義
  3. 不要覆蓋內建名稱:避免使用 listdictstr 等作為變數名
  4. 謹慎使用 global 和 nonlocal:它們會增加程式碼的複雜度
# 不好的做法
result = None

def calculate():
    global result
    result = 42

calculate()
print(result)

# 好的做法
def calculate():
    return 42

result = calculate()
print(result)