Python命名空间
??在现实应用中,我们可以将Python 语言中的命名空间理解为一个容器。在这个容器中可以装许多标识符,不同容器中同名的标识符是不会相互冲突的,它们有相互的对应关系。在Python程序中,使用命名空间来记录变量的轨迹。命名空间是一个字典(Dictionary),它的键就是变量名,它的值就是对应变量的值。
一、命名空间的本质
??在Python程序中,通常会存在如下三个可用的命名空间。 ??(1)每个函数都有自己的命名空间,这被称为局部命名空间,它记录了函数的变量,包括函数的参数和局部定义的变量。 ??(2)每个模块拥有自己的命名空间,这被称为全局命名空间,它记录了模块的变量,包括函数、类、其他导入的模块、模块级的变量和常量。 ??(3)还有就是内置命名空间,任何模块均可访问它,它存放着内置的函数和异常。 ??要想理解 Python语言的命名空间,首先需要掌握如下所示的三条规则。 ??(1)赋值(包括显式赋值和隐式赋值)产生标识符,赋值的地点决定标识符所处的命名空间。 ??(2)函数定义(包括def和 lambda)产生新的命名空间。 ??(3) Python搜索一个标识符的顺序是“LEGB”。所谓的“LEGB”,是指Python语言中4层命名空间的英文名字首字母的缩写,具体说明如下所示。 ??●最里面的1层是L (local),表示在一个函数定义中,而且在这个函数里面没有再包含函数的定义。 ??●第2层E (enclosing functio), 表示在一个函数定义中,但这个函数里面还包含有函数的定义,其实L层和E层只是相对的。 ??●第3层G (global) ,是指一个模块的命名空间,也就是说在一个.py文件中定义的标识符,但不在一个函数中。 ??●第4层B (builtin) ,是指Pylo解释器启动时就已经具有的命名空间,之所以叫builtin是因为在Python解释器启动时会自动载入__builtin__模块,这个模块中的list、str 等内置函数处于B层的命名空间中。 ??注意:在Python程序中,可以通过模块来管理复杂的程序,而将不同功能的函数分布在不同的模块中,函数及其全局命名空间决定了函数中引用全局变量的值。函数的全局命名空间始终是定义该函数的模块,而不是调用该函数的命名空间。因此,在函数中引用的全局变量始终是定义该函数模块中的全局变量。 ??例如在下面的实例代码中,演示了函数与其全局命名空间关系的过程。实例文件mo.py是一个模块文件,在里面定义了全局变重name和函数moo_fun(),并在函数moo_fun()中输出了全局变量name 的值。文件mo.py 的具体实现代码如下所示。
name= "Moo Module" #定义变量name的初始值
def moo_fun(): #定义方法moo fun()
print('函数moo_fun: ') #打印显示文本
print('变量name: ',name) #打印显示变量name的值
??实例文件test.py是一个测试文件,调用了模块mo中的方法moo_fun()。在此文件中也定义了全局变量name和函数 bar(),并在函数bar()中输出了全局变量name 的值。然后分别调用本模块中定义的函数 bar()和从mo模块中导入的函数moo_fun(),最后还定义一个把函数作为参数传入并调用的函数call_moo_fun()。因为函数中引用的全局变量始终是定义该函数模块中的全局变量,所以第一次调用输出了当前模块中的全局变量name的值;而第二次调用从mo模块中导入的函数moo_fun(),输出的则是在mo模块中的全局变量name的值。第三次调用call_moo_fun()函数,并把从mo模块中导入的函数moo_fun()作为参数传入其中进行调用,即使是在函数内部被调用,它仍然输出函数moo_fun()模块中全局变量name的值。实例文件test.py 的具体实现代码如下所示。
from mo import moo_fun #调用模块mo中的方法moo fun ()#定义全局变里name
name='current module'
def bar(): #定义函数bar()
print ('当前模块中函数bar: ') #打印显示文本
print ('变量name: ', name) #打印显示变量name的值
def call_moo_fun (fun) : #定义方法cal1_moo_fun ()
fun ()
if __name__== '__main__': #输出当前模块中的全局变量name的值
bar()
print() #调用从mo模块中行入的函数moo_fun
moo_fun ()
print()
call_moo_fun (moo_fun)
??在运行程序后,第1次输出的是当前模块中的全局变量name的值’current module’,第2次输出的是mo模块中的全局变量name的值“Moo Module”,第3次输出的仍然是mo模块中的全局变量name的值“Moo Module”。执行后会输出:
二、查找命名空间
??在Python程序中,当某一行代码要使用变量x的值时,会到所有可用的名字空间去查找这个变量,按照如下所示的顺序进行查找。 ??(1)局部命名空间:特指当前函数或类的方法。如果函数定义了一个局部变量x,或一个参数x,Python程序将便用它,然后停止搜索。 ??(2)全局命名空间:特指当前的模块。如果模块定义了一个名为x的变量,函数或类,Python将使用它然后停止搜索。 ??(3)内置命名空间:对每个模块都是全局的。作为最后的尝试,Python将假设x是内置函数或变量。 ??(4)如果Python在上述命名空间找不到x,它将放弃查找并引发一个NameError异常,例如 NameError: name ‘aa’ is not defined。 ??在Python程序中,嵌套函数命名空间的杳找顺序比较特殊,具体说明如下所示。 ??(1)先在当前(嵌套的或lambda)函数的命名空间中搜索 ??(2)然后在父函数的命名空间中进行搜索。 ??(3)接着在模块命名空间中搜索。 ??(4)最后在内置命名空间中搜索。 ??例如在下面的实例代码中,演示了嵌套函数命名空间的查找过程。
info="2024奥运会主办地:" #定义全局变量的初始值
def func_father (country): #定义嵌孕函数func_father()
def func_son(area): #此处的city变量,覆盖了父函数的city变重
city="巴黎"
print(info + country + city + area)
city="洛杉矶"
func_son("台区"); #调用内部函
func_father("法国")
在上述实例代码中,info在全局命名空间中,country在父函数的命名空间中,city和 area在自己函数的命名空间中。执行后会输出:
三、命名空间的生命周期
??在Python程序中,在不同的时刻创建不同的命名空间,这些命名空间会有不同的生存期。具体说明如下所示。 ??(1)内置命名空间在Python解释器启动时创建,会一直保留下去,不会被删除。 ??(2)模块的全局命名空间在模块定义被读入时创建,通常模块命名空间也会一直保存到解释器退出。 ??(3)当函数被调用时创建一个局部命名空间当函数返回结果或抛出异常时被删除。每一个递归调用的函数都拥有自己的命名空间。 ??Python语言有一个自己的特别之处,在于其赋值操作总是在最里层的作用域。赋值不会复制数据,只是将命名绑定到对象而已。删除操作也是如此,例如“del y”只是从局部作用域的命名空间中删除命名“y”而已。而事实上,所有引入新命名的操作都作用于局部作用域。请看下面的演示代码,因为在创建命名空间时,Python 会检查代码并填充局部命名空间。在并把它添加到局部命名空间中。当函数执 Python运行那行代码之前,就发现了对i的赋值行时,Python解释器认为i在局部命名空间中,但是没有值,所以会产生错误。
i=1
def func2():
i=i+1
func2();
执行结果报错:
四、命名空间访问函数locals()与globals()
??在Python程序中访问命名空间时,不同的命名空间用不同的方式进行访问,具体说明如下所示。 ??(1)局部命名空间。 ??在Python程序中可以使用内置函数locals()来访问局部命名空间。例如在下面的实例命名代码中,演示了使用内置函数 locals()来访问局部命名空间的过程。 具体实现代码如下所示。
def funcl(i,str): #定义函数func1 ()
X=1234 #定义变量x的值是12345
print(locals()) #访问局部命名空间
funcl(1,"firs")
执行后会输出: ??(2)全局(模块级别)命名空间。 ??在Python程序中,可以使用内置函数globals()来访问全局(模块级别)命名空间。 ??例如在下面的实例代码中,演示了使用内置函数globals()来访问全局命名空间的过程。
import copy #导入copy模块
from copy import deepcopy #导入deepcopy
gstr = "global string" #定义变量gstr
def funcl(i,info): #定义函数func1 ()
x = 12345
print(locals()) #访问局部命名空间
funcl(1,"first")
if __name__=="__main__":
print("the current scope's global variablest:")
dictionary=globals() #访问全局(模块级别)命名空间
print (dictionary)
??执行后的效果如下图所示,在此需要注意,执行效果中的某些输出结果会因用户的测试环境的差异而不同。
??注意:通过上述执行效果可知,模块的名字空间不仅仅包含模块级的变量和常量,还包括所有在模块中定义的函数和类。除此以外,还包括任何被导入到模块中的东西。另外也可以看到,内置命名也同样被包含在一个模块中, 它被称作__builtins__ 。当使用import module时,模块自身被导入,但是它保持着自已的名字空间,这就是为什么需要使用模块名来访问它的函数或属性module.fumction的原因。但是当使用from module import function时,实际上是从另一个模块中将指定的函数和属性导入到名字空间中,这就是为什么我们可以直接访问它们却不需要引用它们所来源的模块。在使用globals函数时,会真切地看到这一切的发生。 ??在Python程序中,使用内置函数locals()和globals()是不同的。其中locals是只读的,而globals则不是只读的。例如在下面的实例代码中,演示了locals与globals之间的区别。
def func1 (i,info) : #定义函数func1 ()
x = 41 #定义x的初始值
print (locals()) #访问局部命名空间
locals()["A国"] = 23 #locals是只读的
print ("A国=",x)
y=55 #定义y的初始值
func1 (1,"first")
globals() ["B国"]= 34 #globals不是只读的
print("B国=",y)
执行后会输出:
??在Python程序中,locals 实际上没有返回局部名字空间,它返回的是一个拷贝。 所以对它进行改变对局部名字空间中的变量值并无影响。而globals返回实际的全局名字空间,而不是一个拷贝。所以对globals 所返回的dictionary的任何改动都会直接影响到全局变量。
|