變數範圍


在Python中,變數無需宣告就可以直接使用並指定值,除非特別使用globalnonlocal指明,否則變數範圍(Scope)總是在指定值時建立。例如:
>>> x = 10
>>> y = 10
>>> def some():
...     x = 20
...     print(x)
...     print(y)
...
>>> some()
20
10
>>> print(x)
10
>>> print(y)
10
>>>


在 上例中,some()函式中的x,其實是在函式範圍中所建立的新x,其遮蔽(shadow)了外部的x範圍,所以你所設定的是區域變數x,指定值給變數時,就是在該範圍中 建立該變數,就是指這樣的情況。如果取用變數時,也是從當時的範圍開始尋找,若沒有,才找尋外部範圍,例如上例中,取用x時,在區域範圍找到區域變數x, 所以顯示的是區域變數x的值,而不是外部變數x,取用y時,由於區域範圍找不到,所以取得外部y的值。

記得變數範圍的建立是在指定時發生,而不是在取用時發生,所以像下面這個例子:
>>> x = 20
>>> def some(x = x):
...     print(x)
...
>>> some()
20
>>> some(30)
30
>>> print(x)
20
>>>


是定義了一個some()函式,將當時外部變數x的值20設定給參數x作為預設值。

實際上,在Python中變數可以在四個範圍中建立或尋找。內建(Builtin)、全域(Global)、外包函式(Endosing function)、函式(Local functon)。一個例子如下:
  • demo.py
x = 10         # 全域

def outer():
y = 20 # 在 outer() 函式範圍

def inner():
z = 30 # 在 inner() 函式範圍
print(x) # 內建範圍 print,全域的 x
  print(y) # 內建範圍 print,外包 outer() 函式的 y

print(x) # 內建範圍 print,全域的 x

取用名稱時(而不是指定),一定是從最內層往外尋找。Python中所謂全域,實際上是以模組檔案為界,以上例來說,x實際上是demo模組範圍中的變數,不會橫跨所有模組範圍。

注意,到print名稱,實際上它是內建範圍,在Python 3中有個builtins模組,該模組中的變數,會自動被所有的模組所擁有。例如:
>>> import builtins
>>> dir(builtins)
['ArithmeticError', 'AssertionError', 'AttributeError', 'BaseException', 'Buffer
Error', 'BytesWarning', 'DeprecationWarning', 'EOFError', 'Ellipsis', 'Environme
ntError', 'Exception', 'False', 'FloatingPointError', 'FutureWarning', 'Generato
rExit', 'IOError', 'ImportError', 'ImportWarning', 'IndentationError', 'IndexErr
or', 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'NameError',
 'None', 'NotImplemented', 'NotImplementedError', 'OSError', 'OverflowError', 'P
endingDeprecationWarning', 'ReferenceError', 'RuntimeError', 'RuntimeWarning', '
StopIteration', 'SyntaxError', 'SyntaxWarning', 'SystemError', 'SystemExit', 'Ta
bError', 'True', 'TypeError', 'UnboundLocalError', 'UnicodeDecodeError', 'Unicod
eEncodeError', 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserW
arning', 'ValueError', 'Warning', 'WindowsError', 'ZeroDivisionError', '__build_
class__', '__debug__', '__doc__', '__import__', '__name__', '__package__', 'abs'
, 'all', 'any', 'ascii', 'bin', 'bool', 'bytearray', 'bytes', 'chr', 'classmetho
d', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'div
mod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozens
et', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int
', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map'
, 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'pr
int', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr'
, 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'va
rs', 'zip']
>>>


所以許多Python中可以直接使用的函式,其名稱實際上是在builtins模組之中。基本上,你也可以將變數建立至builtins中。例如:
import builtins
import sys
builtins.argv = sys.argv
print(argv[1])

變數指定值的同時就確立其所在的範圍。如果你希望指定值的變數是全域範圍的話,則可以使用global指明(雖然並不鼓勵全域變數)。例如:
>>> x = 10
>>> def some():
...     global x
...     x = 20
...
>>> some()
>>> print(x)
20
>>>


來看看以下這個會發生什麼事情?
>>> x = 10
>>> def some():
...     print(x)
...     x = 20
...
>>> some()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in some
UnboundLocalError: local variable 'x' referenced before assignment
>>>


在some()函式中,print(x)中的x其實是some()函式中的區域變數x,因為範圍建立總是在指定時發生,在some()函式的第二行中,有個x指定,所以Python直譯器看到的是這個x,而你在指定x值之前,就要顯示其值,就會發生錯誤。上面這個錯誤可以這麼修改:
>>> x = 10
>>> def some():
...     global x
...     print(x)
...     x = 20
...
>>> some()
10
>>>


在Python 3中新增了nonlocal,可以讓你指明變數並非區域變數,請依照函式(Local functon)、外包函式(Endosing function)、全域(Global)、內建(Builtin)的順序來尋找,即使是指定運算。例如:
x = 10
def outer():
x = 100 # 這是在 outer() 函式範圍的 x
def inner():
nonlocal x
x = 1000 # 改變的是 outer() 函式的 x
inner()
print(x) # 顯示 1000

outer()
print(x) # 顯示 10

因此,在 lambda 運算式 看過的一個例子:
>>> def func():
...     x = 10
...     def getX():
...         return x
...     def setX(n):
...         x = n
...     return (getX, setX)
...
>>> getX, setX = func()
>>> getX()
10
>>> setX(20)
>>> getX()
10
>>>


在 上例中,func()中的setX()宣告的x,其實是setX()中的區域變數x,其遮蔽了外部func()的x,所以你的n是指定給區域變數x。如果你要改變func()中的x值,則可以在setX()函式中宣告nonlocal。例如:
>>> def func():
...     x = 10
...     def getX():
...         return x
...     def setX(n):
...         nonlocal x
...         x = n
...     return (getX, setX)
...
>>> getX, setX = func()
>>> getX()
10
>>> setX(20)
>>> getX()
20
>>>