初探變數範圍
April 21, 2022Python 的變數不用事先宣告,一個名稱在指定值時,就可以成為變數,並建立起自己的作用範圍(Scope)。
變數尋找/建立
在取用一個變數時,會看看目前範圍中是否有指定的變數名稱,若無則向外尋找,因此在函式中可取用全域(Global)變數:
>>> x = 10
>>> def func():
... print(x)
...
>>> func()
10
>>>
在上面的例子中,func
中沒有區域變數 x
,因此往外尋找而取得全域範圍建立的變數 x
。如果在 func
中,對名稱 x
作了指定值的動作呢?
>>> x = 10
>>> def func():
... x = 20
... print(x)
...
>>> func()
20
>>> print(x)
10
>>>
在 func
中進行 x = 20
的時候,其實就建立了 func
自己的區域變數 x
,而不是將全域變數 x
設為 20,因此在 func
執行完畢後,顯示全域變數 x` 的值仍會是 10。
變數範圍
就目前而言可以先知道的是,變數可以在內建(Builtin)、全域(Global)、外包函式(Endosing function)、區域函式(Local function)中尋找或建立。一個例子如下:
func scope_demo.py
x = 10 # 建立全域 x
def outer():
y = 20 # 建立區域 y
def inner():
z = 30 # 建立區域 z
print('x = ', x) # 取用全域 x
print('y = ', y) # 取用 outer 函式的 y
print('z = ', z) # 取用 inner 函式的 z
inner()
print('x = ', x) # 取用全域 x
print('y = ', y) # 取用 outer 函式的y
outer()
print('x = ', x) # 取用全域 x
取用名稱時(而不是對名稱指定值),一定是從最內層往外尋找。Python 中的全域,實際上是以模組檔案為界,以上例來說,x
實際上是 scope_demo
模組範圍中的變數,不會橫跨其他模組。
經常使用的 print
名稱,是屬於內建範圍,在 Python 有個 builtins
模組,該模組中的名稱範圍,橫跨各個模組。例如:
>>> import builtins
>>> dir(builtins)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'BlockingIOError', 'BrokenPipeError', 'BufferError', 'BytesWarning', 'ChildProcessError', 'ConnectionAbortedError', 'ConnectionError', 'ConnectionRefusedError', 'ConnectionResetError', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'EnvironmentError', 'Exception', 'False', 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError' …略
dir
函式可用來查詢指定的物件上可取用的名稱。Python 可以直接使用的函式,其名稱有在 builtins
模組定義。
global/nonlocal
Python 還有個 globals
,可以取得全域變數的名稱與值,在全域範圍呼叫 locals
時,取得結果與 globals
是相同的。
如果對變數指定值時,希望是針對全域範圍的話,可以使用 global
宣告。例如:
>>> x = 10
>>> def func():
... global x, y
... x = 20
... y = 30
...
>>> func()
>>> x
20
>>> y
30
>>>
來看看以下這個會發生什麼事情?
>>> x = 10
>>> def func():
... print(x)
... x = 20
...
>>> func()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in func
UnboundLocalError: local variable 'x' referenced before assignment
>>>
在 func
函式中有個 x = 20
的指定,python
直譯器會認為,print(x)
中的 x
是 func
函式中的區域變數 x
,因為範圍內有指定 x
的陳述句,就流程而言,在指定區域變數 x
的值之前,就要顯示其值是個錯誤。如果真的想顯示全域的 x
值,可以在 print(x)
前一行,使用 global x
宣告。
當然,無論是哪種程式語言,除非是概念上真的是全域的名稱,否則都不鼓勵使用全域變數,因此應避免 global
宣告的使用。
Python 有個 nonlocal
,可以指明變數並非區域變數,請直譯器依照區域函式、外包函式、全域、內建的順序來尋找變數,就算是指定運算時,也要求是這個順序。例如:
x = 10
def outer():
x = 100 # 這是在 outer 函式範圍的 x
def inner():
nonlocal x
x = 1000 # 改變的是 outer 函式的 x
inner()
print(x) # 顯示 1000
outer()
print(x) # 顯示 10
Python 沒有區塊範圍變數,因此在流程控制語法區塊中建立的變數,離開區塊之後也可以使用:
>>> if True:
... x = 10
...
>>> print(x)
10
>>>
變數範圍的討論,雖然略嫌無趣,然而若沒有搞清楚相關規則,很容易就發生名稱衝突,導致一些不可預期的臭蟲,不可不慎。目前暫時是先針對一個模組檔案中相關的範圍進行探討,之後有機會還會探討其他有關範圍的議題。