0x00 前言
在这个系列的开始,介绍了Python文件操作相关的概念,这一节我会说一说对Python的作用域与名称空间的理解。
很多同学可能会觉得说的东西比较基础似乎和安全没有什么关系,其实不然。我是这么理解的,know it,then hack it.真正牛逼的漏洞都是对程序本身有深入的理解后挖出来的,『人肉扫描器』没啥意思,只有真正弄懂了基础,再去分析漏洞报告,做到举一反三,我相信0day也会随之而来。
同时,我更希望看到的是随着SDL的落地,安全意识的提高,从源头上“堵住”漏洞,走出『挖漏洞,补漏洞』的怪圈。
不罗嗦了,开始今天的主题。
0x01 Python的名称空间
什么是名称空间?
简单来说,名称空间就是存放名字与值绑定关系的地方。
这里说的名字与值,不单单指变量名与变量值之间的关系。函数名、类名等均为这里所指的名字。
名称空间是内存中一块隔离的空间。
名称空间还方便了大型项目的协作开发,Python之禅中也提到,“名称空间是一个伟大的发明,在日常开发中,我们要尽可能多的使用它。”
名称空间的分类:
名称空间共分为3类,
1、全局名称空间
2、局部名称空间
3、内置名称空间
注:变量名、函数名、类名等都是文中所指的名字。
内置名称空间,就是在Python解释器启动时生效,在Python解释器关闭时失效。像一些常见的函数,比如说print()、len()、max()等均存在于内置名称空间。
全局名称空间,定义的是文件级别的名字与值之间的绑定关系。举个例子,比如说我们在当前文件中定义变量x=1、函数def fuck:pass,等诸如此类,未在函数或类中定义的变量与值之间的绑定关系就是全局名称空间。这类名字与值之间的绑定关系,直接在Python文件的上下文中定义,未在函数或类的内部定义。
局部名称空间,定义的是函数内部名字与值之间的绑定关系。
在启动Python解释器时,内置名称空间随之生效。此时可以直接调用一些内置的函数,而不需要对该类函数进行定义。当Python解释器关闭时,内置名称空间随之失效。
执行Python文件时,Python解释器会将该Python文件级别的名字与值之间的绑定关系存放起来,存放该绑定关系的空间就是全局名称空间。当该Python文件执行完毕时,全局名称空间也随之失效。
在调用函数时,局部名称就会临时生效。函数调用结束,局部名称空间随之失效。
需要注意的是,类中定义的名字与值之间的绑定关系也可以理解为局部名称空间。与函数中的名称空间不同,类定义完,该名称空间就会产生。除非类被删除,否则这块名称空间不会被回收。
可见,名称空间的加载顺序如下:
1、先加载内置名称空间
2、再加载全局名称空间
3、最后有可能加载局部名称空间
反之,Python解释器查找名字的顺序为,先局部,再全局,最后内置。
0x02 作用域
作用域就是一个作用范围,简单理解,也跟找名字有关。
众所周知,Python的三块名称空间有:内置名称空间、全局名称空间、局部名称空间。
这几个内存空间的特点如下:
拿内置名称空间来说,
生效:在Python解释器启动时,内置名称空间生效。
失效:在Python解释器关闭时,内置名称空间失效。
那全局名称空间呢?
生效:执行Python文件时,全局名称空间生效。
失效:当Python文件执行完毕时,全局名称空间失效。
局部名称空间:
生效:当函数被调用时,局部名称空间临时生效。
失效:函数调用结束,局部名称空间失效。
围绕Python的名称空间,其作用域一共分为两块:全局作用域、局部作用域。
全局作用域由内置名称空间、全局名称空间两部分的名字与值之间的绑定关系组成。
其特点为:全局存活,全局有效,伴随Python文件执行始终。
局部作用域由局部名称空间的名字与值之间的绑定关系构成。
其特点为:临时存活,局部有效。
总结一下,名字的查找顺寻为LEGB,即locals -> enclosing function -> globals -> builtins。
locals:代表函数内部的名字空间,包括函数内部的局部变量,和形式参数。
enclosing:外部函数嵌套的名字空间,这种形式常见于闭包中。
globals:全局变量,即文件级别定义的名字与值之间的绑定关系。函数定义所在模块的名称空间。
builtins:内置模块的名字空间。
查看作用域的两个内置函数:globals()、locals()。
globals(),用来查看全局名称空间的名字。
dir(globals()['__builtins__']),用来查看内置名称空间的名字。
在内置名称空间中,存在诸如print、max、len等名字,这就是为什么我们可以直接调用这些函数,而不需要进行定义的原因。
locals(),用来查看局部名称空间的名字。
在文件级别而不是在函数中调用locals()查看名字时,与globals()的返回值相同。
作用域关系,在函数定义时就已经确定,与调用位置无关。
x = 23333
def f1():
def f2():
print(x)
return f2
f = f1()
print(f)
f()
def func():
x = 123
f()
func()
如上代码,由于作用域关系在函数定义时就已经确定了,即f()函数的作用域关系在定义时确定x = 23333(全局名称空间),与调用位置x=123(局部名称空间)无关,所以最后的输出结果仍为23333而不是123。
0x03 关键字global和nonlocal的引入
x = 1
def foo():
x = 10
foo()
print(x)
该代码的执行结果为:1
x = 1
def foo():
global x
x = 10
foo()
print(x)
该代码的执行结果为:10
在函数内部(局部名称空间),要想修改全局名称空间内变量的值需要使用关键字global声明。
x = 1
def foo():
x = 2
def foo2():
nonlocal x
x = 13213321
foo2()
print('foo:'+str(x))
foo()
print(x)
该代码的执行结果为:
foo:13213321
1
在嵌套定义的函数中,要想修改上一级函数中(上一级局部名称空间)变量的值,需要使用关键字nonlocal进行声明。若上一级不存在该变量,则继续向上一级寻找,直到找到为止(仅限在函数内部)。找不到则报错。
0x04 参考链接
9. Classes - Python 3.6.4 documentation