[Python学习日记-38] Python 中的函数的名称空间
简介
名称空间
作用域查找顺序
简介
在前面学习函数的时候我们发现,函数内部也有一个内存空间是用于存储函数自己的一些变量的,及时这个变量名与外部的变量名一样是也没关系,Python 会优先调用内部的变量,后面我们也进一步学习了作用域,解释为每个变量和函数都有自己的作用域,其实变量和函数的作用域的不用就是我们这次要说的名称空间决定的,下面我们一起来看看名称空间到底是怎么一回事吧。
名称空间
名称空间(Name Space),顾名思义就是存放名字的地方,那它到底是存什么东西的名字呢?那我们举例说明一下:若变量 x=1,1存放于内存中,那名字 x 存放在哪里呢?没错,名称空间正是存放名字 x 与1绑定关系的地方。
Python 里面有4种名称空间,每个地方都有自己的名称空间,互不干扰,不同空间中的两个相同名字的变量之间没有任何联系。
Python 中的名称空间有:LEGB
- locals:函数内部的名称空间,一般包括函数的局部变量以及形式参数(形参)
- enclosing function:在嵌套函数中外部函数的名称空间,例如,fun2 嵌套在 fun1 里,对 fun2 来说,fun1 的名字空间就是 enclosing。
- globals:当前的模块空间,模块就是一些 py 文件。也就是说,globals() 类似全局变量。
- __builtins__:内置模块空间,也就是内置变量或者内置函数的名称空间,使用 print(dir(__builtins__)) 可查看包含的值,如下图所示
不同变量的作用域不同就是由这个变量所在的名称空间决定的。作用域即范围有:
- 全局范围:全局存活,全局有效
- 局部范围:临时存活,局部有效
我们可以使用以下方法来查看不同作用域的名称空间,代码如下
# 全局范围
print(globals())
# 局部范围
print(locals())
代码输出如下:
在同一层级的情况下,全局变量和局部变量并无差别,那我们来创建一个函数,在函数内部看看会怎么样吧,代码如下
a = "this is gloabls."
def is_local():
b = "this is locals."
print(locals())
print(globals())
is_local()
代码输出如下:
作用域查找顺序
当程序引用某个变量的名字时,就会从当前名字空间开始搜索。搜索顺序规则便是:LEGB。即 locals -> enclosing function -> globals -> __builtins__。会进行一层一层的查找,找到了之后,便停止搜索,如果最后没有找到,则会抛出在 NameError 的异常。为了验证这一过程请看下面的代码
level = "L0"
n = 22
def func():
level = "L1"
n = 33
print(locals())
def outer():
level = "L2"
n = 44
print("outer:",locals(),n)
def inner():
level = "L3"
print("inner:",locals(),n) # 此处打印的 n 会是多少呢?
inner()
outer()
func()
代码输出如下:
从输出可知,当本层的名称空间当中有 n 时会优先查找本层的,像 L1 和 L2 中的输出都是如此,但是当本层没有 n 时,它将会去外层查找,像 L3 就是如此,它所输出的 n 就是 L2 中的44。如此类推,如果这个时候把 L2 中的 n 注释了,那么 L2 和 L3 都会输出 L1 中的 n,如下代码所示
level = "L0"
n = 22
def func():
level = "L1"
n = 33
print(locals())
def outer():
level = "L2"
# n = 44
print("outer:",locals(),n)
def inner():
level = "L3"
print("inner:",locals(),n) # 此处打印的 n 会是多少呢?
inner()
outer()
func()
代码输出如下:
如我们所料,L2 和 L3 都输出了 L1 的 n,即33。 但是在这里都只是嵌套函数内部的查找,还没有涉及到全局的,即 locals -> enclosing function,这个时候我们如下也也把 L1 的 n 也注释掉了会不会就直接显示全局里面的 n 呢?来看看下面的代码
level = "L0"
n = 22
def func():
level = "L1"
# n = 33
print(locals(),n)
def outer():
level = "L2"
# n = 44
print("outer:",locals(),n)
def inner():
level = "L3"
print("inner:",locals(),n)
inner()
outer()
func()
代码输出如下:
这个过程就是 locals -> enclosing function -> globals 了,有的同学就会问到,那什么时候才会用到 __builtins__ 呢?这个就要涉及到内置函数了,在这里我们就用 dir() 为例,看下面的代码
level = "L0"
print(dir())
dir = 22
print(globals())
def func():
level = "L1"
print(locals(),dir)
def outer():
level = "L2"
print("outer:",locals(),dir)
def inner():
level = "L3"
print("inner:",locals(),dir)
inner()
outer()
func()
代码输出如下:
从上面的代码中,我们发现我们把原来的 dir() 函数进行了重定向,在 globals() 里面变成了变量 dir = 22,这个时候后面的函数调用 dir 的时候全部都变成了22,这个时候就体现了 globals -> __builtins__了,弄清楚了这个优先级关系,在后面的开发当中就不会因为嵌套函数多起来之后手忙脚乱了。