Python-名称空间与作用域
一、名称空间(namespaces)
名称空间namespacs:字面意思看,是存放名字的地方。
我们知道,变量名是存放在栈区的,而值是存放在堆区的。准确的说,栈区存放的是变量名与其对应的值的内存地址的映射/绑定关系。(如下图所示)
所谓名称空间,就是对栈区的划分,可以理解为对栈区里这么多映射/绑定关系划了划片,归了归类。
有了名称空间之后,就可以在栈区中存放相同的名字了,在程序执行期间最多会存在三种名称空间。
-
内置名称空间(只有一个)
伴随python解释器的启动/关闭而产生/回收,因而是第一个被加载的名称空间,用来存放一些内置的名字,比如内置函数名。
存放的名字:存放的是python解释器内置的名字
''' >>> print <built-in function print> >>> input <built-in function input> '''
存活周期:python解释器启动则产生,python解释器关闭则销毁
-
全局名称空间(只有一个)
伴随python文件的开始执行/执行完毕而产生/回收,是第二个被加载的名称空间,文件执行过程中产生的名字都会存放于该名称空间中。
存放的名字:只要不是在函数内定义的,也不是python内置的,剩下的都是全局名称空间的名字
存活周期:python文件开始运行则产生,python文件运行完毕后销毁
-
局部名称空间(可以有多个)
伴随函数的调用/结束而临时产生/回收,函数的形参、函数内定义的名字都会被存放于该名称空间中。
存放的名字:在调用函数时,运行函数体代码过程中产生的函数内的名字
存活周期:在调用函数时存活,函数调用完毕后则销毁
名称空间的加载顺序:内置名称空间>全局名称空间>局部名称空间
名称空间的销毁顺序:局部名称空间>全局名空间>内置名称空间
名字的查找优先级:从当前所在的位置(空间)向上一级一级查找。
-
如果当前在局部名称空间:局部名称空间—>全局名称空间->内置名称空间
def fun(): print(x) x = "我在全局名称空间" fun() # 我在全局名称空间
代码像上面这样写居然没有报错,我们以为x没有在函数内被定义,直接输出就会报错,但事实上是在局部名称空间里没找着x,去了全局名称空间找到了。
x = '我在全局名称空间' def func(): print(x) def foo(): x = '我在局部名称空间,归属于foo函数' func() foo() # 我在全局名称空间
名称空间的"嵌套"关系是以函数定义阶段为准,与调用位置无关。也就是说,按照上面的代码,func函数在foo函数里被调用,此时按照惯性思维,我们认为func函数里的x会去foo函数内部找,其实不是这样的。上面代码里表现的这种嵌套关系只是语法上的嵌套,而名称空间的嵌套关系是在函数定义完成的那一刻就确定的,所以func函数内部的局部名称空间的上一层级是全局名称空间。
-
如果当前在全局名称空间:全局名称空间->内置名称空间
print(id(print())) # 140711167694976
名字的查找顺序是以定义阶段为准的,从当前位置开始往外一层一层找,跟在哪调用完全无关。(函数多层级嵌套也是,从所在嵌套层级一层一层往外找)
二、作用域(作用范围)
根据三种不同的名称空间,我们划分了两个作用域:
-
全局作用域:位于全局名称空间、内置名称空间中的名字属于全局作用域(全局作用范围),该范围内的名字全局存活(除非被删除,否则在整个文件执行过程中都存活)、全局有效(在任意位置都可以使用,被所有函数共享)。
-
局部作用域:位于局部名称空间中的名字属于局部作用域(局部作用范围)。该范围内的名字临时存活(即在函数调用时临时生成,函数调用结束后就释放)、局部有效(只能在函数内使用)。
Python支持函数的嵌套定义,在内嵌的函数内查找名字时,会优先查找自己局部作用域的名字,然后由内而外一层层查找外部嵌套函数定义的作用域,若都没有找到,则查找全局作用域。
三、global与nonlocal
x = '我是全局的x'
def func():
x = '我是局部的x'
func()
print(x) # 我是全局的x
如果在局部想要修改全局的名字对应的值(不可变类型),需要用global。
x = '我是全局的x'
def func():
global x # 声明x这个名字是全局的名字
x = '我是局部的x' # 这里直接改变的是全局变量x,如果全局有则直接改变,如果没有则创建
func()
print(x) # 我是局部的x
如果在局部想要修改全局的名字对应的值是可变类型,则可以直接改变。
l = [111, 222]
def func():
l[0] = 100
func()
print(l) # [100, 222]
要明确注意:通过global改变的是全局的名字对应的值。
x = 0
def f1():
x = 11
print('f2执行前f1内的x:', x)
def f2():
global x
x = 22
print('f2里的x:',x)
f2()
print('f2执行后f1内的x:', x)
print('f1执行前全局的x:', x)
f1()
print('f1执行后全局的x:', x)
# 程序运行结果:
# f1执行前全局的x: 0
# f2执行前f1内的x: 11
# f2里的x: 22
# f2执行后f1内的x: 11
# f1执行后全局的x: 22
在上面函数嵌套中,f2函数里改变了全局x的值,但f1里的x是不受影响的。如果我们想在f2函数中修改f1函数里的x,该怎么做呢?
nonlocal(了解): 修改函数外层函数包含的名字对应的值(不可变类型)
x=0
def f1():
x=11
def f2():
nonlocal x
x=22
f2()
print('f1内的x:',x)
f1() # f1内的x: 22
nonlocal是从当前层函数的外一层开始找,一层一层嵌套函数找下去,当在最后一层函数里仍没找到则会报错:
x=0
def f1():
def f2():
nonlocal x
x=22
f2()
print('f1内的x:',x)
f1() # 程序报错
如果针对的位置是可变类型则可以直接改变:
def f1():
x=[]
def f2():
x.append(1111)
f2()
print('f1内的x:',x)
f1() # f1内的x: [1111]