A namespace is a mapping from names to objects
命名空间是名字和对象的映射。可以简答地把namespace理解为一个字典,实际上很多当前的Python实现namespace就是用的字典。各个命名空间是独立的,没有任何关系的,因此一个命名空间中不能有重名,但不同的命名空间是可以重名而没有任何影响。
常见的命名空间包括:
命名空间有创建时间和生存期,具体而言:
可见,一个模块的引入,函数的调用,类的定义都会引入命名空间,函数中的再定义函数,类中的成员函数定义会在局部namespace中再次引入局部namespace。
A scope is a textual region of a Python program where a namespace is directly accessible.
作用域是Python中某个命名空间中的名字可以被直接引用的上下文区间。
在Python中并不是所有的语句块中都会产生作用域。只有当变量在Module(模块)、Class(类)、def(函数)中定义的时候,才会有作用域的概念。
具体而言,常见的作用域由内到外(按照程序的层级关系)包括:
显然,作用域的分类与命名空间是一一对应的。在【命名空间】中曾提及不同的命名空间是可以重名而没有任何影响。 那么当在不同的命名空间中取相同的变量名时,程序又是如何区分的呢?这就涉及到变量调用的搜索顺序 :
在python中,变量不需要事先声明,赋值后即可调用使用。调用的法则遵从LEGB法则,其中L为local,E为enclosing,G为global,B为built-in,即变量首先在局部搜索,比如一个函数中,如果没有搜寻到,则继续在enclosing中搜寻,比如果还是没有,则搜索全局变量,如果还是没有,就搜索内建变量名,最终找不到的话,则抛出异常。这符合从底层到顶层的设搜索计逻辑。
值得注意的是,如果定义了nonlocal(见下文),那么将不遵从LEGB法则,而直接从enclosing中搜索。
locals
是Python的内置函数,可以方便的查看函数会以字典类型返回当前位置的全部局部变量。
nonlocal
和global
是Python中两个关键词,均用于在函数定义中澄清变量的作用域。下面来详细介绍下nonlocal
和global
的基本用法。
(1)直接引用global变量
首先定义一个嵌套函数,解释器在解释时遇到内部变量x,会按照LEGB的顺序直接查找,直到在global namespace中找到x,返回其引用的常数对象值。
x = 5
def outer():
def inner():
y = x+5 # 按照LEGB的顺序寻找变量名x,发现为global变量
print(y)
return inner
if __name__=='__main__':
func = outer()
func() # Output: 10
(2)函数内赋值同样名称的变量
此时解释器会将其作为局部变量(无视global中的x)。
x = 5
def outer():
def inner():
x = -5
y = x+5 # 按照LEGB的顺序寻找变量名x,发现在local中有定义
print(y)
return inner
if __name__=='__main__':
func = outer()
func() # Output: 0
(3)函数内先引用,再重新赋值
此时在本地命名空间内,解释器发现同时有赋值和引用操作,会认为这是局部变量,而在引用前又未进行赋值初始化,因此会找不该局部变量而报错UnboundLocalError
。
x = 5
def outer():
def inner(): # local namespace中同时有赋值和引用操作,解释器会认为是局部变量
y = x+5 # x 作为局部变量并未先赋值初始化,因此报错!
print(y)
x = -5
return inner
if __name__=='__main__':
func = outer()
func() # UnboundLocalError: local variable 'x' referenced before assignment
要在不改变上述程序逻辑顺序的同时修正错误,可以显式声明为global
对象。
(4)显示声明global
x = 5
def outer():
def inner():
global x # 声明为全局变量
y = x+5
print(y)
x = -5 # 重新为全局变量赋值
print(x)
return inner
if __name__=='__main__':
func = outer()
func() # Output: 10 -5
(5)可变对象的情况
以上(1)-(4)对比了不可变变量时,是否采用global
的异同,而对于可变对象,也遵循相同的原则,因此这里不给出具体的示例,而直接给出相关原则。
基本原则:
a) 若在local scope内直接引用全局变量,则无需显式声明global,可直接使用;
b) 若在local scope内只直接操作全局变量(可变变量时),则全局变量会同步结果;
c) 若在local scope内只为同样名称的变量重新赋值,则视为是local变量,后续操作与外部定义的同名全局变量无任何关系;
d) 若local scope内先对全局变量进行操作,在对同名变量进行赋值操作,则会报错。
nonlocal是用来界定local变量和enclosing变量(嵌套函数中外部函数中定义的变量)的关键词,其原则与local变量和global变量间的原则相同,这里不加赘述。
相同点:两者都是为了规定变量的作用域,从而避免变量定义和搜索时的错误。
不同点:
(1)两者的功能不同。global关键字标识全局变量,对该变量进行修改就是修改全局变量,而nonlocal关键字标识该变量是上一级函数中的局部变量,如果上一级函数中不存在该局部变量,nonlocal位置会发生错误(最上层的函数使用nonlocal修饰变量必定会报错)。
(2)两者使用的范围不同。global关键字可以用在任何地方,包括最上层函数中和嵌套函数中,即使之前未定义该变量,global修饰后也可以直接使用,而nonlocal关键字只能用于嵌套函数中,并且外层函数中定义了相应的局部变量,否则会发生错误(见(1))。
最后,给出一个包含局部变量、嵌套变量和全局变量三类变量的例子供参考:
def scope_test():
def do_local():
spam = "local spam" #此函数定义了另外的一个spam字符串变量,并且生命周期只在此函数内。此处的spam和外层的spam是两个变量。
def do_nonlocal():
nonlocal spam # 使用外层的spam变量
spam = "nonlocal spam" # 修改了外层的spam变量
def do_global():
global spam
spam = "global spam"
spam = "test spam" # 外部函数中定义的enclosing变量
do_local()
print("After local assignmane:", spam) # 首先搜索到enclosing变量
do_nonlocal() # 为enclosing变量重新赋值
print("After nonlocal assignment:",spam) # 仍然是enclosing变量
do_global()
print("After global assignment:",spam) # 仍然是 enclosing变量
scope_test()
print("In global scope:",spam) # 函数外面,因此是global变量
# After local assignmane: test spam
# After nonlocal assignment: nonlocal spam
# After global assignment: nonlocal spam
# In global scope: global spam
在前面global
和nonlocal
的介绍中,涉及到Python中的两个重要概念:赋值和引用,这里做简单的介绍。
Assignments do not copy data — they just bind names to objects.
在Python中,对象是独立的,不同作用域中的不同名字都可以被绑定在同一个对象上,当然对这个对象的修改会影响所有的引用。赋值操作就是名字和对象的绑定或重绑定。
函数调用的参数传递是赋值(也可以说是引用),不是拷贝。
引用就是指变量名指向内存中一块对象的关系,赋值的过程就是引用。
【Reference】