在編程裡,其中一個最重要的概念是函數範圍(scope of variable)。這是一個怎樣的概念?
比如說,我們有麥當勞與肯德基的外送餐單。麥當勞的餐點裡有麥樂雞套餐,而肯德基的菜單裡有家鄉雞套餐。
如果我們想點麥樂雞套餐的話,我們不能致電肯德基的外送熱線,因為只有麥當勞有這個餐點。
從編程的角度,麥樂雞套餐只是局部地定義(locally defined)在麥當勞的餐單。當您嘗試在肯德基的餐單尋找(reference)麥樂雞套餐,會回傳錯誤。
到底在 Python 裡的 local scope 是如何運作的?
局部定義 (Local)
相信大家對局部定義(local scope)的函數不太陌生,因為只要您寫過一個 Python 的自訂功能便會接觸過。

def customFunction():
local_variable = 10
print('在 customFunction 裡呼喚 local_variable 回傳:', local_variable)
customFunction()
print('在 customFunction 外呼喚 local_variable 回傳:', local_variable)
註:先按一下綠色按鈕 “Run” 執行代碼,讓您能在 IPython Shell 看到編程結果!
在以上的例子,我們在一個 Python 自訂功能 customFunction() 裡定義了 local_variable,並使之等於 10。
當我們在 customFunction() 裡列印 local_variable(第 3 行),可以成功獲取 10 的回傳,但當我們在 customFunction() 外列印 local_variable(最後 1 行),Python 卻會回傳錯誤。
在 customFunction() 外列印 local_variable 就如在肯德基點麥樂雞套餐,當然不能。
原理是,當我們踏進(step into)customFunction() 時,Python 會建立一個名為 local_variable 的函數。但當我們離開(exit)customFunction() 後,Python 便會銷毀這個 local_variable 的函數,以致您不能繼續使用 local_variable。
所以,我們的例子最後一行出現了 NameError 的錯誤,因為於 Python 而言,local_variable 是一個未定義(undefined)的函數。
假如我們先在 customFunction() 外定義了 local_variable,再嘗試在 customFunction() 裡改變其值(value)可以嗎?這就牽涉到局部 vs 總體(local vs global)的問題了。
總體定義 (Global)

some_variable = 20 # 總體定義 Global scope
def customFunction():
some_variable = 10 # 局部定義 Local scope
print('在 customFunction 裡呼喚 some_variable 回傳:', some_variable)
customFunction()
print('在 customFunction 外呼喚 some_variable 回傳:', some_variable)
註:先按一下綠色按鈕 “Run” 執行代碼,讓您能在 IPython Shell 看到編程結果!
以上的例子說明,即便我們嘗試在自訂功能裡更改功能外的函數(從 20 改為 10),離開自訂功能後,函數值依然是 20。
進階地說,這是因為開頭的 some_variable 函數與 customFunction() 裡的 some_variable 指向不同的物件/記憶體位址(RAM location)。所以,我們在 customFunction() 裡局部定義(local scope)的 some_variable 其實與外面總體定義(global scope)的 some_variable 不同。
進階:檢查函數的記憶體位址

some_variable = 20 # 總體定義 Global scope
def customFunction():
some_variable = 10 # 局部定義 Local scope
print('在 customFunction 裡的 some_variable 記憶體位址:', id(some_variable))
customFunction()
print('在 customFunction 外的 some_variable 記憶體位址:', id(some_variable))
註:先按一下綠色按鈕 “Run” 執行代碼,讓您能在 IPython Shell 看到編程結果!
我們可以簡單地檢查這 2 個 some_variable 的記憶體位址。在以上例子,我們使用 id() 這個功能檢查函數的記憶體位址(RAM location),會發現其實這兩個 some_variable 的位址不同。因此,我們不能直接在自訂功能裡更改總體定義(global scope)的函數。
如果我非要在自訂功能裡更改總體定義的函數,可以嗎?
救星:global 關鍵字

variable_1 = 20 # 總體定義 Global scope
variable_2 = 50 # 總體定義 Global scope
def customFunction():
global variable_1 # 將 variable_1 指向總體定義
variable_1 = 10
variable_2 = 40 # 局部定義 Local scope
print('在 customFunction 裡的 variable_1 記憶體位址和值:', id(variable_1), '|', variable_1)
print('在 customFunction 裡的 variable_2 記憶體位址和值:', id(variable_2), '|', variable_2)
customFunction()
print('在 customFunction 外的 variable_1 記憶體位址和值:', id(variable_1), '|', variable_1)
print('在 customFunction 外的 variable_2 記憶體位址和值:', id(variable_2), '|', variable_2)
註:先按一下綠色按鈕 “Run” 執行代碼,讓您能在 IPython Shell 看到編程結果!
我們其實可以使用一個關鍵字,global,便可以在自訂功能裡更改總體定義(global scope)的函數。
在以上的例子,我們定義了 2 個函數,而我們同樣嘗試在 customFunction() 裡更改這 2 個函數的值。
不同的是,我們只在 customFunction() 的開首裡加入 global variable_1。
| 值 | variable_1 | variable_2 |
|---|---|---|
| customFunction() 內 | 10 | 40 |
| customFunction() 外 | 10 | 50 |
從上圖可見,只有 variable_1 在 customFunction() 裡和外的值是一樣的 10,而 variable_2 則不同。
這是因為我們透過 global variable_1 指示 Python 把 variable_1 變成一個可以在自訂功能裡存取的函數,以致我們在 variable_1 = 10 時,系統不會製造出新的局部定義函數(local scope variable),而是直接更改在 customFunction() 外定義的 variable_1。
至於 variable_2,雖然我們亦嘗試在 customFunction() 把值從 50 改為 40,但因為我們沒有加入 global variable_2,以致系統把一個新的局部定義函數設為 40,而非把總體定義的 variable_2 改為 40。
因此,當我們在 customFunction() 外呼叫 variable_2 時,系統便會把總體定義的 variable_2 回傳,而這個 variable_2 不被我們在 customFunction() 內的更改影響。
進階:檢查 global 函數的記憶體位址
| 記憶體位址 | variable_1 | variable_2 |
|---|---|---|
| customFunction() 內 | 93913040984864 | 93913040985824 |
| customFunction() 外 | 93913040984864 | 93913040986144 |
我們也可以用記憶體位址印證我們剛才的推論。
當我們使用 global variable_1 時,我們在 customFunction() 裡所更改的 variable_1 便會是總體定義 (global scope)的 variable_1。所以,我們可以在 customFunction() 裡和外見到同一個記憶體位址。
但我們沒有使用 global variable_2,亦因此系統在 customFunction() 裡處理 variable_2 = 40 時,悄悄地建立了新的局部定義函數(local scope variable)。所以,我們在在 customFunction() 裡和外見到不同的記憶體位址。
應用:猜數字遊戲

import random
from sys import exit
min_x = 1
max_x = 100
answer = random.randint(min_x,max_x)
def loop():
global min_x, max_x
while True:
guess = input('請從 ' + str(min_x) + ' 到 ' + str(max_x) + ' 猜一個數字:')
if guess.isnumeric():
guess = int(guess)
break
if guess == answer:
exit('恭喜您猜對了!答案是 ' + str(answer))
elif (guess < answer) and (min_x < guess):
min_x = guess
elif (guess > answer) and (max_x > guess):
max_x = guess
while True:
loop()
相信大家都會玩過這種猜數字的遊戲:我的答案是 75,而我先告訴您這個數字是 1 至 100 中間。如果您猜的是 70(少於我的答案),我再告訴您這個數字在 70 至 100 中間,如此類推。
我們在 Python 裡也嘗試寫出這個遊戲。我們會在另一篇教學詳細講解這個例子,但我們先把重點放到以上 loop() 的功能裡。
由於我們每次都需要根據 guess 去更新 min_x 和 max_x 的數值(例如您猜了 70,我便會把 min_x 改為 70;您再猜了 90,我便會把 max_x 改為 90),所以我們需要找個辦法在每次猜的時候把 min_x 和 max_x 的數值保存。
其中一個方法就是使用 global 關鍵字了!只要我們使用 global min_x, max_x 這句,便可以在每次猜數字(即是使用 loop() 功能)的時候更新他們的數值,使這個小遊戲能夠運行。
當然,我們也有更好的方法去更新 min_x 和 max_x。如果您還是不太清楚這個小遊戲的編程,或想知道如何更好地編程,可以閱讀這篇教學:實例1(上):猜數字遊戲!Colab 例子談編程設計和處理異常。
教學完整代碼
最後送給大家這篇教學的 Google Colab 完整代碼。如果您不懂得使用免安裝又好用的 Google Colab Notebook,記得閱讀這篇教學了:新手 1/3:5 分鐘免安裝學習 Python?Google Colab Notebook 幫緊您!
結語
希望您透過這個教學能了解更多有關局部定義(local scope)和總體定義(global scope)函數的分別,並能夠在需要的時候使用 global 關鍵字突破這個限制吧!




















