Python 的变数不用事先宣告,一个名称在指定值时,就可以成为变数,并建立起自己的作用范围(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
模组定义。
全局/非局部
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
>>>
变数范围的讨论,虽然略嫌无趣,然而若没有搞清楚相关规则,很容易就发生名称冲突,导致一些不可预期的臭虫,不可不慎。目前暂时是先针对一个模组档案中相关的范围进行探讨,之后有机会还会探讨其他有关范围的议题。