一:python作用域基础
作用域就是指命名空间,python创建、改变或查找变量名都是在所谓的命名空间中。变量在赋值创建时,python中代码赋值的地方决定了这个变量存在于哪个命名空间,也就是他的可见范围。
对于函数,函数为程序增加一个额外的命名空间层来最小化相同变量之间的冲突:默认情况下,一个函数内赋值的所有变量名都与该函数的命名空间相互关联。这条规则意味着:
- 在def内赋值的变量名只能被def内的代码使用。不能在函数的外部引用该变量名。
- 在def内赋值的变量名与在def外赋值的变量名并不冲突,即使是相同的变量名,也完全时两个完全不同的变量。
- 如果一个变量在def内赋值,它对于该函数时局部的
- 如果一个变量在外层的def中赋值,它对于内层的函数时非局部的
- 如果一个变量在所有的def外赋值,它对整个文件来说时全局的。
可见,一个变量的作用域取决于它在代码中被赋值的位置,而与函数调用完全无关
作用域细节
函数的命名空间时可嵌套的,以便函数内部用的变量名不会与函数外(同一模块或另一函数中)的变量名冲突。函数定义了局部作用域,而模块定义了全局作用域:
- 外围模块是全局作用域。这里的全局指的是文件顶层的变量名对于这个文件是全局的,全局作用域的作用范围仅限于单个文件。python中不存在一个跨文件的单一且无所不包的全局作用域的概念,全局是对模块而言的。
- 赋值的变量名除非被声明为global或nonlocal,否则均为局部变量。在函数内部想更改顶层变量的值,就不许声明为全局变量。
- 函数的每次调用就会创建一个新的局部作用域。可以认为每一个def及lambda语句都定义了一个新的局部作用域,但局部作用域实际上对应于一次函数的激活,每一次激活的调用都能拥有一套自己的局部变量副本。
注意:一个函数内部任何类型的赋值都会把一个名称划定为局部的,包括=语句,import的模块名,def的函数名,函数形式参数名等。如果你在def中以任何方式赋值一个名称,他都会默认为该函数的局部名称。
但是,要注意原位置改变对象并不会把变量划分为局部变量,只有对变量赋值才可以。例如L在模块顶层被赋值为列表,在一个函数内类似L.append(X)的语句并不会将L划分为局部变量,而L = X却可以。append是修改对象,而=是赋值,要明白修改一个对象并不是对一个变量名赋值。
变量名解析:LEGB规则
总结三条简单的规则
- 在默认情况下,变量名赋值会创建改变局部变量
- 变量名引用至多可以在四种作用域内进行查找:首先是局部,其次是外层的函数,之后是全局,然后是内置
- 使用global和nonlocal语句声明的名称将赋值的变量名分别映射到外围的模块和函数的作用域,变成全局。
变量名解析机制称为LEGB规则,也是有作用域的命名而来的:
- 当你在函数内使用未限定的变量名时,python将查找4个作用域并在找到该变量名的地方停下:分别是局部作用域L,向外一层的def或lambda的局部作用域E,全局作用域G,最后时内置作用域B。如果都没有找到就会报错。
- 当你在函数中给一个变量名赋值时,python会创建或改变局部作用域的变量名,除非该变量名已经在该函数中被声明为全局变量。
- 当你在所有函数的外面给一个变量名赋值时,就是在模块文件的顶层或时交互式命令行下,此时的局部作用域就是全局作用域,即该模块的命名空间。
内置作用域
是一个名为builtins的内置模块,但是必须要导入之后才能使用内置作用域
global语句
global语句告诉python生成一个或多个全局变量,总结一下全局变量:
- 全局变量是在外层模块文件的顶层被赋值的变量名
- 全局变量如果是在函数内赋值的话,必须经过声明
- 全局变量名在函数的内部不经过声明也可以被引用
程序设计中最少化全局变量,因为你可能也不知道语句在什么时候最后一次执行,值是多少;最小化跨文件的修改,变量的值可能化发生难以预测的改变甚至报错。
二:作用域和嵌套函数
这里的作用域主要是作用域查找规则LEGB的第二个层次E,即def或lambda定义的外层函数的局部作用域。嵌套函数产生相对应的嵌套作用域,对应嵌套的代码结构。
嵌套的函数作用域中变量查找规则,在一个函数中:
- 引入一个变量X,X依然遵循LEGB规则,然而global声明会直接从全局(模块文件)作用域查找。
- 赋值(X=value)会创建或改变当前作用域中的变量名X。若X在函数的声明为全局变量,它会创建或改变整个模块中作用域的X;若在函数内声明为非局部变量,会修改最近的嵌套函数作用域中的X。
在存在嵌套函数时,若global存在,global声明会将变量映射到外层模块,嵌套函数中变量也许会被引用,但他们必须要nonlocal声明才能修改。
嵌套作用域举例
f1调用f2,f2会引用存在于外层f1的局部作用域的变量X的值
nonlocal语句
nonlocal和global都遵循LEGB准则,但是限制了查找规则。
使用global语句意味着名称存在于外层的模块中,global使得作用域查找从外围的作用域开始,并且允许对那里 的名称赋值。如果名称不存在该模块中,作用域查找会继续进行到内置作用域,但是全局名称的赋值总是在模块的作用域中创建或修改它们。
nonlocal语句将作用域查找限制为只在外层的def中,同时要求名称已经存在,并且允许对它们赋值,作用域不会继续查找到全局或内置作用域。nonlocal也意味着忽略局部作用域。
nonlocal应用
默认情况下,不允许修改外层def作用域中的名称,返回:
UnboundLocalError: local variable 'state' referenced before assignment
注:这里以闭包的方式传入label的值,类似于类的简单实现。