4.3 Python的作用域
作用域就是能够起作用的范围。在Python中,并不是所有的语句块都会产生作用域,只有当变量在模块、类、函数中定义的时候才会有作用域的概念,因此变量、函数和类都有作用域。
1.块级作用域
块级作用域指在最小单元中的局部语句作用域。见示例4-17。
#1.块级作用域 s1='s1是块级外定义的变量。' print(s1) if 1>0: s = "if块中定义的变量s" s1='s1是块级内定义的变量。' print(s) print(s1) for i in range(10): if i==2: m = i print('for语句中定义的i= ', i) print('for语句中的if语句块定义的m= ', m)
程序运行结果:
s1是块级外定义的变量。 if块中定义的变量s s1是块级内定义的变量。 for语句中定义的i= 9 for语句中的if语句块定义的m= 2
在示例4-17中,变量s是在if语句中定义的,但是在if语句外可以使用。
变量s1在if语句外定义,但是在if语句内部却把变量s1修改了。
for语句定义了子变量i, for语句内的if语句定义了变量m,那么在for语句外就能输出或使用变量i和变量m。
从上面的结果中看到,变量的定义和赋值都是同一个作用域的同一个块在if-elif-else, for-else, while, try-except, try-finally(其中不能含def语句和class语句)等关键字的语句块中并不会产生新的作用域。因此,块作用域中的变量在整个块中有效,其他语句可以创建、使用或修改这些变量。
2.函数级作用域
函数级作用域是指最小函数体的作用域。在Python 3中,因为函数体内可以定义子函数,因此这里指无定义子函数的函数体。见示例4-18。
#2.函数体中的作用域 s1='s1是块级定义的变量。' def func(): s1='s1是函数内定义的变量。' print(’函数内输出s1:'+s1) func() #执行函数 print(’函数外输出s1:'+s1)
程序运行结果:
函数内输出s1:s1是函数内定义的变量。 函数外输出s1:s1是块级定义的变量。
从运行结果中我们可以看到,在函数体外定义了一个变量s1,函数体内对变量s1进行赋值,并显示结果为“s1是函数内定义的变量”。
函数运行结束再次输出变量s1的值为“s1是块级定义的变量”,这说明函数体外的变量s1和函数体内的变量s1不相同,因此它们不在同一个作用域。
再看一个程序,示例4-19。
#3.函数体中的作用域 s1='s1是块级定义的变量。' def func(): print(’函数内输出s1: '+s1) func() #执行函数 print(’函数外输出s1: '+s1)
程序运行结果:
函数内输出s1: s1是块级定义的变量。 函数外输出s1: s1是块级定义的变量。
上面结果说明,函数体可以使用其外部定义的变量s1,但是不能修改。
再看示例4-20,如果在函数体内定义变量s 2,那么在函数体外使用会是什么结果?
#4.函数体中的作用域 def func(): s2='s2是函数级定义的变量。' print(’函数内输出s2:'+s2) func() #执行函数 print(’函数外输出s2:'+s2)
程序运行结果:
print(’函数外输出s2:'+s2) NameError: name 's2' is not defined)
程序运行出错,提示变量s 2没有定义。这说明函数体内定义的变量仅仅在函数体内有效,函数运行结束,变量消失,后面再想使用这些变量就会出现错误。
由此可见,函数体内的变量和函数体外的变量不在一个作用域。
3.作用域的类型
在Python中,当使用一个变量时并不严格要求预先声明,但必须绑定到当前作用域中。
Python的作用域分为L(Local、局部)作用域、E(Enclosing、嵌套)作用域、G(Global、全局)作用域和B(Built-in、内置)作用域。
L作用域是在Python函数体内创建使用的变量或定义的函数体,因为只有当前函数和函数体中定义的子函数才能够访问。由于Python没有Local关键字,因此没有用Global定义的变量都是局部变量。Python中也有递归,即自己调用自己,每次调用都会创建一个新的局部命名空间。在函数内部的变量声明中,使用Global关键字的为全局变量,其他均为局部变量。
E作用域包含在def关键字闭包函数中。E作用域和L作用域是相对的,即E作用域是相对于更上层的函数L作用域而言的。但区别在于,对于一个函数,L作用域是定义在此函数内部的局部作用域,而E作用域是定义在此函数的上一层父级函数的局部作用域,其主要是为了实现Python的闭包而增加的。
G作用域是在Python当前模块文件中代码运行时就创建的变量。在当前模块的所有函数都能引用或访问的变量为全局变量,即定义为全局作用域变量名,其在Python当前文件中任意位置都能使用。在当前文件任意位置中用Global语句定义的变量,文件中每一个模块都能使用这些变量。建议尽量少定义全局变量,因为全局变量在模块文件运行的过程中会一直存在,占用内存空间。
B作用域:即系统内固定模块里定义的变量,如预定义在Built-in模块内的变量。
4.变量名解析LEGB法则
LEGB法则:以L, E, G, B的规则查找,即在局部找不到,就去局部外的局部找(如闭包),再找不到就去全局找,然后再去内建中找。
当在函数中使用未确定的变量名时,Python会按照优先级依次搜索4个作用域,以此来确定该变量名的意义。首先搜索局部作用域,然后是上一层嵌套结构中def函数或lambda函数的嵌套作用域,接着是全局作用域,最后是内置作用域,即搜索变量名的优先级为局部作用域 > 嵌套作用域 > 全局作用域 > 内置作用域。
在Python 3解释器查找变量时,会按照顺序依次查找,直到找到变量则停止查找,但如果查找完没有找到对应的变量,则抛出“NameError: name 'xxxx' is not defined”的异常。
下面这个例子比示例4-20仅仅在函数体中多了一条“global s 2”。见示例4-21。
#5.函数体中的作用域 def func(): global s2 s2='s2是函数级定义的变量。' print(’函数内输出s2:'+s2) func() #执行函数 print(’函数外输出s2: '+s2)
程序运行结果:
函数内输出s2:s2是函数级定义的变量。 函数外输出s2: s2是函数级定义的变量。
从上面的显示结果中看到,在函数体中使用Global语句,其后面的变量为全局变量。模块文件.py中的Global变量在该模块文件中都存在作用域。
5.命名空间
命名空间是为了防止不同的人在编写类和库时发生命名冲突而设计的。命名空间可以使变量、函数、类的名称作用在本空间内,而其他空间仍可以使用同样的名称。就好比不同的文件夹下可以有相同的文件名一样,但在相同的文件夹下不能有相同的文件名,命名空间就相当于一个虚拟的文件夹。
Python 3的LEGB法则仅仅适合同一个模块的变量的作用域,不同文件的作用域是不相同的。因此,我们可以使用模块名或模块的别名作为命名空间,这样就可以实现不同模块之间的变量数据共享了。
一般我们开发的系统都是由很多模块文件构成的。例如,后面给大家介绍的小白量化分析系统就是由很多模块文件构成的,不同模块可以共享或者修改系统默认参数变量。
小白量化投资系统模块的全局变量都在HP_global.py模块文件中,其中模块文件HP_set.py对它的变量进行初始化赋值。其他模块文件,只要是包含import HP_global as g语句的就能获取命名空间“g”的所有全局变量值,也可以进行修改这些变量。
见示例4-22。
import HP_global as g ''' #----------------------------------- 下面是HP_global的部分内容 #运行系统环境设置 #os=1 windows, =2 linux, =3 mac oS global os #操作系统 global pyver #Python版本 #软件名称设置 global name #软件名称 global title #软件标题 global developer #软件开发者 global ver #软件版本号 #软件数据目录 global datapath #数据目录 global prgpath #软件目录 #----------------------------------- ''' import HP_set ''' #----------------------------------- 下面是HP_set的部分内容 ##数据主目录 g.datapath='\\xbdata' g.prgpath='\\xb' #软件名称 g.root=None g.name=’小白量化投资平台’ g.title=’小白量化投资平台(1.00版) g.ico='tt.ico' g.winW=1200 g.winH=760 g.ver=1.01 g.user='18578755056' g.login=False g.os=1 #----------------------------------- ''' #g. name是g命令空间的全局变量,整个运行期间所有模块都能访问和修改 #我们利用这个模块来获取这些定制的值 print(’软件名称: ', g.name) print(’软件标题: ', g.title) print(’下面修改g命名空间变量’) g.name=’荷蒲证券分析研究平台’ g.title=’荷蒲证券分析研究平台(1.00版)' print(’软件名称: ', g.name) print(’软件标题: ', g.title)
程序运行结果:
软件名称: 小白量化投资平台 软件标题: 小白量化投资平台(1.00版) 下面修改g命名空间变量 软件名称: 荷蒲证券分析研究平台 软件标题: 荷蒲证券分析研究平台(1.00版)
从上面的结果中看到,示例4-22中的程序修改了命名空间“g”的全局变量。因此,在运行的程序中任何一个模块文件都能获取或修改命名空间“g”的全局变量。