二、函数作用域
1、作用域基础
- 当在程序中使用变量名时,Python创建、改变或查找变量名都是在命名空间(保存变量名的地方)中进行。
- 在默认情况下,一个函数的所有变量名都是与函数的命名空间相关联的。
- 在代码中变量名被赋值的位置决定了这个变量名能被访问到的范围。
- 函数为程序增加一个额外的命名空间层:
-
- 默认情况下,一个函数的所有变量名都是与函数的命名空间相关联的。意味着:
- 一个在def内定义的变量名能够被def内的代码使用。不能在函数的 外部引用这样的变量名。
- def之中的变量名与def外的变量名并不冲突。
- 每个函数调用都会创建作用域。函数返回时,这个局部作用域内赋值的所有变量就丢失了。
- 默认情况下,一个函数的所有变量名都是与函数的命名空间相关联的。意味着:
- 在任何情况下,一个变量的作用域总是由代码中 被赋值的地方所决定,并且与函数调用完全没有关系。
- 变量可以在3个不同的地方分配,分别对象3中不同的作用域:
-
- 如果一个变量在def内赋值,则被定为在这个函数内;
- 如果在嵌套的def中赋值,对嵌套的函数来说,它是非本地的。
- 如果在def外赋值,它是整个文件全局的。
- 区分一个变量是处于局部作用域还是全局作用域:如果变量在全局作用域中使用(即在所有函数之外),它是全局变量。如果在一个函数中,有针对该变量的global语句,它是全局变量。否则,如果该变量用于函数中的赋值语句,它是局部变量。但是,如果该变量没有用在赋值语句中,它是全局变量。
2、作用域法则
函数定义的本地作用域和模块定义的全局作用域之间的关系:
- 内嵌的模块是全局作用域;
- 全局作用域的作用范围仅限于单个文件;在Python中是没有基于一个单个的、无所不包的情景文件的全局作用域的。
- 每次对函数的调用都创建了一个新的本地作用域;本地作用域实际上对应的是函数的调用。
- 赋值的变量名除非生命为全局变量或非本地变量,否则均为本地变量;
- 所有其他的变量名都可以归纳为本地、全局或内置的;
注意点:
1.以交互模式输入的代码遵从作用域规则;但交互模式运行的代码实际真的输入到__main__的内置模块中;这个模块就像一个模块文件一样工作,但结果随着输入而反馈。
2.一个函数内部的任何类型的赋值都会把一个名称划定为本地的:
- 赋值:=;
- import中的模块名称;
- def中的函数名称;
- 函数参数名称;
- 如果在一个def中以任何方式赋值一个名称,都将对于该函数称为本地的。
在原处改变对象并不会把变量划分为本地变量,实际上只有对变量名赋值才可以。名称和对象之间的区分:修改一个对象并不是对一个名称赋值。
3、变量名解析:LEGB原则
总结,对于一个def语句:
- 变量名引用分为三个作用域顺序查找:本地、函数内、全局,内置(内置作用域是一个名为__builtin__的内置模块);
- 默认,变量名赋值会创建或改变本地变量;
- 全局声明和非本地声明将赋值的变量名映射到模块文件内部的作用域
LEGB原则:
- 当在函数中使用未认证的变量名时,Python搜索4个作用域【本地(L),之后上一层结构中def或lambda的本地作用域(E),之后是全局作用域(G),最后是内置作用域(B)】,并在第一处能够找到这个变量名的地方停下来。如果没有搜索到,则报错。
- 当在函数中给一个变量名赋值时(而不是在表达式中引用),Python总是创建 或改变本地作用域的变量名,除非它已经在那个函数中声明为全局变量;
- 当在函数之外给一个变量名赋值时(在模块文件的顶层或在交互模式下),本地作用域与全局作用域(这个模块的命名空间)是相同的。
4、Global语句
- 不是类型或大小的声明,是一个命名空间的声明。
- 声明打算生成一个或多个全局变量名,即存在于整个模块内部作用域的(命名空间)的变量名
-
- 全局变量是位于模块文件内部的顶层的变量名;
- 全局变量如果是在函数内被赋值的话,必须经过声明。
- 全局变量名在函数的内部不经过声明也可以被引用。
- 使用global可以修改一个模块文件的顶层的一个def之外的名称。
- 缺点:当不同的函数通过全局变量变得具有相关性时,会使得程序更难理解和使用
-
模块导入:
- 一个模块文件的全局变量一旦被导入就成为了这个模块对象的一个属性:导入者自动得到了这个被导入的模块文件的所有全局变量的访问权,所以在被导入后,它的全局作用域实际上就构成了一个对象的属性。
-
- 避免导入后互相修改模块的顶层变量,在文件间进行通信最好的办法就是通过调用函数,传递参数然后得到其返回值。
- 其他访问全局变量的方法
5、作用域和嵌套函数
- E,包括了任意嵌套函数内部的本地作用域;
-
- 嵌套的作用域也成为静态嵌套作用域。嵌套,是一个语法上嵌套的作用域,它是对应于程序源代码的物理结构上的嵌套结构。
在增减了嵌套的函数作用域后,变量的查找法则:
- 一个引用(X)首先在本地(函数内)作用域查找变量名X;之后在代码语法上嵌套了的函数中的本地作用域,从内到外查找;之后查找当前的全局作用域(模块文件);最后在内置作用域内(__builtin__)。全局声明将会直接从全局(模块文件)作用域进行搜索。
- 在默认情况下,一个复制(X=value)创建或改变了变量名X的当前作用域。如果X在函数内部声明为全局变量,它将会创建或改变变量名X为整个模块的作用域。另一方面,如果X在函数内声明为nonlocal,赋值会修改最近的嵌套函数的本地作用域中的名称X。
注意:
- 全局声明会将变量映射至整个模块。当嵌套函数存在时,嵌套函数中的变量也许仅仅是引用,但它们需要nonlocal声明才能修改。
6、嵌套作用域和lambda
- lambda表达式引入了新的本地作用域。
- 因为嵌套作用域查找层,lambda能够看到所有在所编写函数中可用的变量。
7、作用域与带有循环变量的默认参数相比较
如果lambda或def在函数中定义,嵌套在一个循环中,并且嵌套的函数引用了一个上层作用域的变量,该变量被循环所改变,所有在这个循环中产生的函数将会有相同的值---在最后一次循环中完成时被引用变量的值。原因:嵌套作用域中的变量在嵌套的函数被调用时才进行查找,所有它们实际上记住的是同样的值(在最后一次循环迭代中循环变量的值)。这是嵌套作用域的值和默认值参数方面遗留的一种仍需要解释清楚的情况,而不是引用所在的嵌套作用域的值。
解决:必须使用默认参数把当前的值传递给嵌套作用域的变量。因为默认参数是在嵌套函数关键时评估的(而不是在其稍后调用时),每一个函数记住了自己的变量i的值。
8、任意作用域的嵌套
作用域可以任意嵌套,但只有内嵌的函数才会被搜索。
9、nonlocal
- nonlocal,声明了将在一个嵌套的作用域中修改的名称;
- 应用于一个嵌套的函数的作用域中的一个名称,而不是所有def之外的全局模块作用;在声明nonlocal名称时,该名称必须已经存在于该嵌套函数的作用域中:它们可能只存在于一个嵌套的函数中,并且不能由一个嵌套的def中的第一个赋值创建。如果使用nonlocal的代码越来越复杂,应该将相关的状态封装成辅助类。
- 与global
-
- global:作用域查找从嵌套的模块的作用域开始,并允许对那里的名称 赋值。如果名称不存在于该模块中,作用域查找继续到内置作用域,但是,对全局名称的赋值总是在模块的作用域中创建或修改它们。
- nonlocal:限制作用域查找只是嵌套的def,要求名称已经存在于那里,并且允许对它们赋值。作用域查找不会继续到全局或内置作用域。
- 边界
在执行nonlocal语句时,nonlocal名称必须在一个嵌套的def作用域中赋值过,否则将会得到一个错误:不能通过在嵌套的作用域中赋给它们一个新值来创建它们。Python必须在函数创建的时候解析nonlocal,而不是函数调用的时候。
- 为什么使用
- nonlocal 语句允许在内存中保持可变状态的多个副本,并且解决了在类无法保证的情况下的简单的状态保持。