Python 作用域 (Scope)(17):名稱能在哪裡被找到?

在上一篇文章中,我們介紹了 命名空間 (Namespace),了解了名稱與對象如何被儲存與管理。


不過,光知道名稱存在哪裡還不夠,程式還需要一套規則來決定「在什麼位置可以使用這些名稱」。這就是 作用域 (Scope) 的任務。

作用域定義了名稱的可見範圍與存活時間。無論是函數中的局部變數、模組中的全域變數,還是 Python 內建的函數,程式在執行時會依照特定的搜尋順序來解析它們。

作用域 (Scope) 介紹

作用域定義了程式在不同部分可以訪問哪些命名空間,以及訪問的順序。

作用域決定了我們程式中哪些名稱可以被訪問

多個命名空間可能同時存在,但並非所有命名空間在程式的各個部分都可訪問

與命名空間類似,Python 有四種不同層級的作用域:內建作用域、全域作用域、封閉作用域和局部作用域

命名空間是存儲名稱-對象對的機制,而作用域則是一套規則系統,決定在程式的哪些位置可以檢索這些名稱。

A diagram showing nested circles representing different scopes in Python, with arrows indicating access patterns between Local, Enclosing, Global, and Built-in scopes

局部作用域 (Local Scope)

局部作用域的特點:

  • 每次呼叫函數時產生
  • 是最深層的作用域
  • 局部作用域中的名稱不能被外部作用域訪問或修改
  • 通常與局部命名空間一一對應

局部作用域限制了變數的可見性,防止不同函數間的變數互相干擾。

def favorite_color():
  color = 'Red'
print(color)  # 錯誤!

# 正確做法:
def favorite_color():
  color = 'Red'
  print(color)  # 正確
favorite_color()
    

第一個例子中,print(color) 在函數外部,無法訪問函數內的局部作用域。

第二個例子中,print(color) 在函數內部,可以訪問到局部變數 color

封閉/非局部作用域 (Enclosing/Nonlocal Scope)

封閉作用域(也稱為非局部作用域)允許嵌套函數訪問外部函數中定義的值。

def outer_function():
  enclosing_value = 'Enclosing Value'
  def nested_function():
    nested_value = 'Nested Value'
    print(enclosing_value)  # 可以訪問外部函數的變數
  nested_function()
outer_function()

封閉作用域有兩個重要限制:

單向流動

作用域訪問只能向上流動。內部函數可以訪問外部函數的變數,但外部函數不能訪問內部函數的變數。

不可修改性

內部函數可以訪問外部函數的不可變對象(如字符串或數字),但無法直接修改它們。

修改作用域行為:nonlocal 語句

Python 提供了 nonlocal 語句,允許我們在嵌套函數中修改外部函數中定義的變數。

不使用 nonlocal:

def enclosing_function():
  var = "value"
  def nested_function():
    var = "new_value"  # 創建新的局部變數
  nested_function()
  print(var)  # 輸出 "value"
enclosing_function()
    

這裡的 var = "new_value" 只是在內部函數中創建了一個新的局部變數,而不是修改外部函數中的 var

使用 nonlocal:

def enclosing_function():
  var = "value"
  def nested_function():
    nonlocal var
    var = "new_value"  # 修改外部變數
  nested_function()
  print(var)  # 輸出 "new_value"
enclosing_function()
    

使用 nonlocal 關鍵字後,內部函數可以修改外部函數中定義的變數。

全域作用域 (Global Scope)

全域作用域的特點:

  • 全域作用域是最高級別的訪問層級
  • 全域命名空間中定義的名稱自動具有全域作用域
  • 全域作用域的名稱可以在程式的任何地方訪問
  • 與封閉作用域類似,默認情況下全域變數可以被訪問但不能被修改
# 全域變數
gravity = 9.8

def get_force(mass):
  return mass * gravity  # 訪問全域變數

print(get_force(60))  # 輸出 588.0
    

全域變數像重力一樣,在整個程式中都有影響,但在局部作用域中嘗試修改它會導致錯誤。

修改作用域行為:global 語句

Python 提供了 global 語句,允許在局部作用域中修改全域變數。

不使用 global

global_var = 10

def some_function():
  global_var = 20  # 創建局部變數

some_function()
print(global_var)  # 輸出 10
    

使用 global

global_var = 10

def some_function():
  global global_var
  global_var = 20  # 修改全域變數

some_function()
print(global_var)  # 輸出 20
    

此外,即使全域命名空間中尚未定義某個名稱,也可以使用 global 語句在全域命名空間中創建它:

def some_function():
 global x # 在全域命名空間中創建 x
 x = 30

some_function()
print(x) # 輸出 30

作用域解析:LEGB 規則

作用域解析是描述 Python 在各種命名空間中搜索名稱的過程。Python 遵循 LEGB 規則,按以下順序搜索名稱:

L (Local)

最先搜索局部作用域(函數內部)

E (Enclosing)

如果在局部找不到,則搜索封閉作用域(外部函數)

G (Global)

如果在封閉作用域找不到,則搜索全域作用域

B (Built-in)

最後搜索內建作用域(Python 預定義的名稱)

如果在這四個作用域中都找不到名稱,Python 會返回 NameError 異常。

Python LEGB scope diagram showing Local, Enclosing, Global, and Built-in scopes as nested circles.

LEGB 規則:實例演示

情況 1:名稱在全域作用域中

age = 27

def func():
  def inner_func():
    print(age)  # 使用全域變數
  inner_func()

func()  # 輸出 27
    

搜索順序:
1. 在 inner_func() 的局部作用域中搜索 age
2. 在 func() 的封閉作用域中搜索 age
3. 在全域作用域中找到 age

情況 2:名稱在多個作用域中

age = 27

def func():
  age = 42  # 在封閉作用域中定義 age
  def inner_func():
    print(age)  # 使用最近的 age
  inner_func()

func()  # 輸出 42
    

搜索順序:
1. 在 inner_func() 的局部作用域中搜索 age
2. 在 func() 的封閉作用域中找到 age,值為 42
3. 不繼續往上搜索

結語

作用域 (Scope) 就像是 Python 的搜尋指南,決定了名稱在哪裡可見、在哪裡不可見。

透過 Local、Enclosing、Global、Built-in 四個層級,Python 建立了一套嚴謹的規則,避免變數互相干擾,也讓程式更具結構性。
理解作用域能幫助你更好地排查錯誤,例如 NameError 或「變數值不如預期」的情況;同時也能讓你靈活運用 globalnonlocal 關鍵字,在需要時修改變數的作用範圍。

Programmer debugging Python code with namespaces and scope visualization tools

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

返回頂端