前面我们理解了变量、函数的作用域,现在我们再深入讨论一下Python是如何给变量、函数划分作用域的。
我们在编写Python程序的过程中,如果要使用变量和函数,都需要先对变量和函数命名后才能使用。Python会把命名后的变量和函数分配到不同的命名空间,并通过名称来识别它们。Python为什么要区分不同的命名空间呢?它有两个作用:一个作用是不同的命名空间对应不同的作用域;另外一个作用是防止命名冲突。
命名空间对应不同的作用域
在函数内部声明的变量属于局部变量,在模块内部声明的变量属于全局变量。Python是如何确定哪个变量是属于全局还是局部呢?这就需要用到命名空间概念了。
Python会把在函数内部声明的变量放置到局部命名空间,把在模块声明的变量放置到全局命名空间。在局部命名空间的变量其作用域只能是在函数内部范围,在全局命名空间的变量其作用域为整个模块。函数的命名也同样适用于局部命名空间和全局命名空间,嵌套函数的命名放置在局部命名空间,因此其作用域只能在父函数范围;而父函数的命名放置在全局命名空间,因此其作用域适用于整个模块。
关于命名空间的作用域,这里面还有一个问题。那就是在模块或函数中使用的Python自身提供的内置函数,它是属于哪个命名空间呢?
因为这些内置函数在模块中随意使用,没有作用域的限制。其实Python还为自己的内置函数、程序提供了一个命名空间,这个命名空间是内建命名空间,在内建命名空间放置的变量、函数,在整个Python程序模块中都可以被访问,其作用域是整个程序。
小结一下,在Python程序执行过程中,会有局部命名空间、全局命名空间和内建命名空间同时存在。局部命名空间记录函数内部的变量、传入函数的参数、嵌套函数等被命名的对象;全局命名空间记录模块的变量、函数、类及其它导入的模块等被命名的对象;内建命名空间记录Python自身提供的函数、模块等被命名的对象。
预防命名冲突
命名空间可以预防变量和函数的命名冲突。Python有三类命名空间,分别是局部命名空间、全局命名空间和内建命名空间。Python在编译和解释执行Python代码的过程中,会为每个模块建立一个全局命名空间,为模块中的每个函数建立局部命名空间。相当于Python为程序的每个模块和函数提供了一个封闭的命名空间,在这个封闭的命名空间中,函数及变量命名互相不受影响,在不同的模块中可以声明相同名称的函数,在不同的函数中可以声明相同名称的变量,虽然它们的名称相同,但它们之间没有任何联系。
那么Python如何把已命名的变量及函数的作用域和命名空间联系起来了呢?它所要做的就是在命名空间查询变量或函数的名称。Python访问一个已命名的变量或函数时,它会从三个命名空间中查询。首先从局部命名空间开始,如果没有找到,它就会继续查找全局命名空间,如果在全局命名空间中也没找到,它将在内建命名空间里查找。如果这些查找都失败了,它将会报出下面的错误。
>>> width
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'width' is not defined
>>>
在Python解释器中,我们输入了width,没有给width进行赋值,创建变量时需要进行赋值,width才会进入Python的全局命名空间。
解释器会从命名空间中查找width,它先从局部空间查找,如果找到了它就会使用局部命名空间的变量width,即使全局命名空间也有变量width。这就很容易理解为什么在函数内部声明的局部变量会覆盖掉在模块中声明的同名变量。
Python解释器在局部命名空间、全局命名空间、内建命名空间都找不到width标识符时,Python解释器给出NameError异常。
查看命名空间的内容
Python提供了内置函数可以输出不同命名空间里面的内容。输出局部命名空间的内容使用locals()函数,输出全局命名空间的的内容使用globals()函数。
查看局部命名空间的内容
函数声明:
locals()
返回局部空间已定义的标识符的符号表,符号表以字典对象返回。如果在模块级别上调用locals()函数,其返回的符号表和globals()函数返回的符号表是相同的内容。
案例代码:
>>> 输出局部命名空间内容
>>> locals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>}
>>> # 定义函数uu
>>> def uu(x):
x += 2
temp = x
# 输出函数uu的局部命名空间
print(locals())
return temp
>>> uu(3)
{'x': 5, 'temp': 5}
5
>>>
查看全局命名空间的内容
函数声明:
globals()
返回全局空间已定义的标识符的符号表,符号表以字典对象返回。全局空间指当前调用该函数模块的全局空间。
案例代码:
locals()函数返回的字典对象中,字典的key为标识符的名称,value为标识符的值。