Python 中名稱的有效範圍 (scope)
Tags: python
Published: 2023-01-05 09:26:33

在 Python 中程式是以[**程式區塊 (code block)**](https://docs.python.org/3/reference/executionmodel.html#structure-of-a-program) 為單位, 每個**模組** (module, 單一腳本檔)、函式本體、類別定義都是一個程式區塊, 有自己紀錄名稱與對應物件的清單, 稱為**名稱空間 (namespace)**, 系統就是依據**程式區塊間的層級關係**, 找到個別名稱要對應到的物件。

## 綁定名稱

每當執行設定敘述、函式定義、類別定義、import 敘述, 以及傳入引數叫用函式或方法時, 就會將名稱[**綁定 (binding)**](https://docs.python.org/3/reference/executionmodel.html#binding-of-names) 到對應的物件, 記錄在該程式區塊的名稱空間內。

在 Python 執行環境中, 預設會有 \_\_builtins\_\_ 名稱空間, 對應到 [builtins 模組](https://docs.python.org/3/library/builtins.html#module-builtins), 包含所有內建的名稱, 像是內建函式 `print` 的名稱就是記錄在這裡。當找不到綁定的名稱時, 最終就會到 \_\_builtins\_\_ 中尋找, 這也是我們可以不用 import 任何名稱就可以叫用 `print` 等內建函式的原因。

## 基本原則：使用在最近一層的程式區塊中綁定的名稱

當使用到某個名稱時, 基本原則就是以[最靠近的程式區塊](https://docs.python.org/3/reference/executionmodel.html#resolution-of-names)中綁定的名稱為準, 例如以下這個簡單的例子：

```python
a = 10

def test():
    b = 20
    print(b)
    print(a)

test()
```

一開始執行模組本身的程式區塊時, 當執行到 `a = 10` 後, 就會紀錄 `a` 名稱綁定到 10 這個整數物件；執行完 `test` 函式的定義後, 也會記錄 `test` 名稱綁定到定義好的函式：

```
__main__
|
| a   -----------> 10
| test-----------> test 函式
+-------
```

Python 會把執行的模組取名為 "\_\_main\_\_", 如果是匯入的模組, 名稱則是檔名。等到叫用 `test()` 時, 由於在目前的名稱空間中就可以找到 `test` 這個名稱, 因此會執行該名稱所綁定到的函式。這時會執行此函式的程式區塊, 並建立該區塊的命名空間, 並在執行 `b = 20` 後記錄 `b` 名稱綁定到 20 這個整數物件：

```
+--__main__--
|
| a   -----------> 10
| test-----------> 函式
|
|    +--test--
|    |
|    | b---------> 20
|    +--------
+------------
```

執行到 `print(b)` 時, 由於在目前的名稱空間中就可以找到 `b` 這個名稱, 因此印出的就是 20。接著執行 `print(a)` 時, 因為在目前的名稱空間中並沒有名稱 `a`, 會往外層的程式區塊中尋找, 所以印出的會是上一層的 `a` 所綁定的 10。

最後的執行結果就會是：

```
# py test.py
20
10
```

這個搜尋名稱的動作是在執行時進行的, 因此即便把 `a = 10` 移到定義 `test()` 函式之後也沒有問題：

```python
def test():
    b = 20
    print(b)
    print(a)

a = 10

test()
```

實際叫用 `test()` 時, 已經綁定 `a` 了, 所以一樣可以找到名稱 `a` 正確執行：

```
# py test.py
20
10
```

## 區域 (local) 與全域 (global) 變數

在特定程式區塊內綁定的名稱, 會在程式區塊結束後跟著消失, 無法使用。舉例來說, 如果在剛剛的範例最後加上 `print(b)`：

```python
a = 10

def test():
    b = 20
    print(b)
    print(a)

test()
print(b)
```

雖然在 `test` 函式中有將 `b` 綁定到 20, 但是在 `test()` 叫用結束後, `b` 這個名稱也消失了, 執行時會因為名稱空間中找不到 `b` 而引發 [`NameError`](https://docs.python.org/3/library/exceptions.html#NameError) 例外：

```
# py test.py
20
10
Traceback (most recent call last):
  File "D:\code\test\test.py", line 9, in <module>
    print(b)
NameError: name 'b' is not defined
```

由於所有的名稱都只在綁定時所在的程式區塊內有效, 因此稱為**區域變數 (local variables)**。對於在模組層級綁定的名稱, 例如前面範例中的 `a`, 因為在模組內的任何地方都可以使用, 也稱它們為**全域變數 (global variables)**, 也就是說, 模組內的名稱既是該程式區塊內的區域變數, 也是模組內的全域變數。如果使用到沒有在所在區塊內綁定的名稱, 例如前述範例中 `test()` 裡面的 `a`, 就稱為**自由變數 (free variable)**。

如果在區塊中有綁定特定名稱, 就會將該名稱視為是區塊內的區域變數, 若在綁定之前就先使用該名稱, 並不會因為找不到該名稱而往外層尋找, 而是會引發 [`UnboundLocalError`](https://docs.python.org/3/library/exceptions.html#UnboundLocalError) 例外, 意思就是尚未綁定的區域變數, 例如：

```python
a = 10

def test():
    b = 20
    print(b)
    print(a)
    a = 30

test()
```

執行結果如下：

```
# py test.py
20
Traceback (most recent call last):
  File "D:\code\python\test.py", line 9, in <module>
    test()
  File "D:\code\python\test.py", line 6, in test
    print(a)
UnboundLocalError: local variable 'a' referenced before assignment
```

這是因為 `a` 是在 `print(a)` 之後才綁定, 即使外層有同名的 `a` 也一樣。

## 內外層同名名稱的處理

由於是從最近一層的程式區塊開始尋找名稱, 所以若是內層與外層有同樣的名稱, 就無法使用到外層的名稱, 例如：

```python
a = 10

def test():
    a = 20
    print(a)
    test1()

def test1():
    a = 30
    print(a)

print(a)
test()
```

一開始執行到 `print(a)` 時, 找到的是模組綁定的名稱 `a`：

```
__main__
|
| a    -----------> 10
| test -----------> test 函式
| test1-----------> test1 函式
+-------
```

因此會印出 10, 到執行 `test` 時, 找到的是函式內綁定的名稱 `a`, 這個名稱和外層模組綁定的名稱 `a` 雖然同名, 但分屬於不同的名稱空間： 

```
__main__
|
| a    -----------> 10
| test -----------> test 函式
| test1-----------> test1 函式
|
|    +--test--
|    |
|    | a ---------> 20
|    +--------
+-------
```

因此印出 20。到執行 `test1` 時, 又綁定了一個新的 `a`, 如下所示：

```
__main__
|
| a    -----------> 10
| test -----------> test 函式
| test1-----------> test1 函式
|
|    +--test--
|    |
|    | a ---------> 20
|    +--------
|
|    +--test1--
|    |
|    | a ---------> 30
|    +--------
+-------
```

因此會印出 30。最後的執行結果如下：

```
# py test.py
10
20
30
```

請特別留意, 程式區塊的層級關係是原始碼的層級關係, 並不是函式之間叫用的關係, 也就是說, 雖然是在 `test()` 內叫用 `test1()`, 但兩者之間並沒有包含的關係。因此, 如果我們把 `test1` 中綁定 `a` 的程式去除, 像是這樣：

```python
a = 10

def test():
    a = 20
    print(a)
    test1()

def test1():
    print(a)

print(a)
test()
```

在 `test1` 中印出的 `a` 就會是外層模組中 `a` 綁定的 10：

```
# py test.py
10
20
10
```

但如果將 `test1` 定義在 `test` 內, 像是這樣：

```python
a = 10

def test():
    def test1():
        print(a)
    a = 20
    print(a)
    test1()

print(a)
test()
```

執行到 `test1` 的時候, 區塊的層級關係會是這樣：

```
__main__
|
| a    -----------> 10
| test -----------> test 函式
| test1-----------> test1 函式
|
|    +--test--
|    |
|    | a ---------> 20
|    |
|    |    +--test1--
|    |    |
|    |    +--------
|    +--------
+-------
```

因此離 `test1` 最近一層就是 `test`, 所以 `a` 名稱綁定的就是 20, 而不是模組內的 10 了：

```
# py test.py
10
20
20
```

如果把 `test` 中綁定名稱 `a` 的設定敘述去除, 像是這樣：

```python
a = 10

def test():
    def test1():
        print(a)
    print(a)
    test1()

print(a)
test()
```

`test1` 就會再往外找到最外層的 `a`, 這樣就會印出 3 個 10 了：

```
# py test.py
10
10
10
```

## 指定使用全域變數或是外層的區域變數

如果你想要使用的是最外層模組的全域變數 `a`, 可以在 `test1` 中使用 `global` 指明要引用的全域變數, 例如：

```python
a = 10

def test():
    def test1():
        global a
        print(a)
    a = 20
    print(a)
    test1()

print(a)
test()
```

這樣系統就會知道在 `test1` 中使用到名稱 `a` 時, 要直接到最外層找, 因此列印的是最外層的 `a` 綁定的 10：

```
# py test.py
10
20
10
```

如果你很明確的要使用外層的區域變數, 而不是最上層模組的全域變數, 可以使用 `nonlocal`, 像是這樣：

```python
a = 10

def test():
    def test1():
        nonlocal a
        print(a)
    a = 20
    print(a)
    test1()

print(a)
test()
```

這樣在 `test1` 中使用的就會是外層 `test` 中的 `a` 了：

```
# py test.py
10
20
20
```

`nonlocal` 並不只是單單往外找一層, 而是會一層層往外找, 例如：

```python
a = 10

def test():
    def test1():
        def test2():
            nonlocal a
            print(a)
        test2()
    a = 20
    test1()

test()
```

在 `test2` 中使用的就是往外兩層在 `test` 中綁定的 `a`, 所以印出的是 20：

```
# py test.py
20
```

你可能會想說, 咦？這樣好像不用特別標示 `nonlocal`, 不就一樣會一層層往外找尋名稱, 為什麼要多此一舉呢？這是因為 `nonlocal` 尋找名稱時, 並不會到最外層的模組找尋全域變數, 以底下的例子來說：

```python
b = 10

def test():
    def test1():
        nonlocal b
        print(b)
    test1()

test()
```

雖然最外層模組有名稱 `b`, 可是因為在 `test1` 中將 `b` 標示為 `nonlocal`, 所以尋找名稱時並不會找到最外層而出現錯誤：

```
# py test.py
  File "D:\code\test\test.py", line 5
    nonlocal b
    ^^^^^^^^^^
SyntaxError: no binding for nonlocal 'b' found`
```

實際上甚至根本都還沒有執行, Python 在編譯程式碼時就發現外層區塊並沒有綁定 `b` 名稱, 因而引發代表語法錯誤的 [**SyntaxError**](https://docs.python.org/3/library/exceptions.html?highlight=syntaxerror#SyntaxError) 例外。

## 縮排並不會建立程式區塊

由於函式的主體需要縮排, 所以會讓人誤以為縮排也會建立一個程式區塊, 像是 C/C++ 程式用大括號建立的區塊那樣。不過事實上, 縮排並不是程式區塊, 在縮排中綁定的名稱就是隸屬於所在的程式區塊, 離開縮排區域後還是存在, 例如：

```python
for i in range(3):
    a = i
    print(i)

print(i)
print(a)
```

在 `for` 迴圈結束後, 不論是隨著 `for` 綁定的 `i` 還是在 `for` 迴圈本體中才綁定的 `a` 都還是有效, 並不會消失。執行結果如下：

```
# py test.py
0
1
2
2
2
```

## 類別定義的程式區塊不包含類別內的方法

前面提過區塊層級是以原始碼而定, 但有個例外, 就是**類別定義的程式區塊並不包含類別中的方法**, 像是以下這個例子：

```python
class A:
    x = 10

    def test(self):
        print(x)

a = A()
a.test()
```

依照往最近的區塊找尋名稱的規則, 在 `test` 方法中找不到的 `x` 應該是往外層找到類定義中綁定的 `x`, 不過實際上這個程式會發生錯誤：

```
# py test.py
Traceback (most recent call last):
  File "D:\code\test\test.py", line 8, in <module>
    a.test()
  File "D:\code\test\test.py", line 5, in test
    print(x)
NameError: name 'x' is not defined
```

這是因為實際上類別定義有它自己的名稱空間, 和類別內的方法之間是獨立的, 你可以將之視為如下：

```
__main__
|
| a    -----------> A 物件
| A    -----------> 類別 A 的定義
|
|    +--A--
|    |
|    | x ---------> 10
|    +--------
|
|    +--a.test--
|    |
|    +--------
+-------
```

在方法中找不到的名稱會往全域變數找, 因此如果在最外層定義 `x`, `a.test` 就會使用最外層的 `x`, 例如：

```python
class A:
    x = 10

    def test(self):
        print(x)

x = 20
a = A()
a.test()
```

執行結果如下：

```
# py test.py
20
```

或者透過 `self` 引用定義在類別中的 `x`：

```python
class A:
    x = 10

    def test(self):
        print(self.x)

a = A()
a.test()
```

印出來的就會是 10 了：

```
# py test.py
10
```

類別定義的命名空間會成為類別自己的特徵值 (attributes), 這可以透過 [object.\_\_dict\_\_](https://docs.python.org/3/library/stdtypes.html?highlight=__dict__#object.__dict__) 查看, 例如：

```python
>>> A.__dict__.keys()
dict_keys(['__module__', 'x', 'test', '__dict__', '__weakref__', '__doc__'])
```

你可以看到 `x` 出現在其中, 我們也可以觀察 `a`：

```python
>>> a.__dict__.keys()
dict_keys([])
```

你會發現是空的集合, 如果透過 `a` 引用 `x`, 會因為 `a` 本身沒有 `x` 可用, 於是再透過 `a.__class__` 往 `A` 尋找而引用到類別定義中的 `x`：

```python
>>> a.x
10

>>> a.__class__
<class '__main__.A'>

>>> a.__class__.x
10
```

如果幫 `a` 物件添加 `x` 特徵值, 那麼 `a.test()` 就會循 `self` 引用到這個 `x`：

```python
>>> a.x = 20
>>> A.x
10

>>> a.test()
20
>>> a.__dict__.keys()
dict_keys(['x'])
```

## 遞迴呼叫的命名空間

前面提過, 每次執行一個程式區塊時, 就會建立一個新的名稱空間, 對於遞迴呼叫的函式, 就會建立多個同一函式的名稱空間, 以底下的例子來說：

```python
def fact(n):
    if n < 2:
        return 1
    return n * fact(n - 1)

print(fact(4))
```

執行到 `fact(4)` 時的名稱空間如下：

```
__main__
|
| fact    -----------> fact 函式
|
|    +--fact(4)--
|    |
|    | n ---------> 4
|    +--------
+-------
```

但是因為遞迴, 會再依序執行 `fact(3)`、`fact()`、`fact(1)`, 名稱空間變成：

```
__main__
|
| fact    -----------> fact 函式
|
|    +--fact(4)--
|    |
|    | n ---------> 4
|    +--------
|
|    +--fact(3)--
|    |
|    | n ---------> 3
|    +--------
|
|    +--fact(2)--
|    |
|    | n ---------> 2
|    +--------
|
|    +--fact(1)--
|    |
|    | n ---------> 1
|    +--------
+-------
```

也就是說, 每次叫用 `fact` 時, 其內的 `n` 都是各自專屬的名稱, 而不是所有的 `fact` 共用同一個 `n`。這個結構會從 `fact(1)` 傳回 1 後依序傳回計算值, 最後得到 `4*3*2*1`, 也就是 24 的值：

```
# py test.py
24
```

## 結語

本文希望透過簡短的文章與圖解, 讓初學者可以分清楚程式中實際使用的名稱到底是哪一個？避免因為用到尚未綁定的名稱、或是用錯名稱導致程式錯誤, 實際上可能還有一些細節, 不過對於一般程式來說, 本文提到的部分應該已經夠用了。