###### tags: `Python` # Python 中名稱的有效範圍 (scope) 在 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 ``` ## 結語 本文希望透過簡短的文章與圖解, 讓初學者可以分清楚程式中實際使用的名稱到底是哪一個?避免因為用到尚未綁定的名稱、或是用錯名稱導致程式錯誤, 實際上可能還有一些細節, 不過對於一般程式來說, 本文提到的部分應該已經夠用了。