Python基础手册24——函数中变量的作用域
五、变量的作用域
当你在一个程序中使用变量名时,Python创建、改变或查找变量名都是在命名空间(一个保存变量名的地方,这个地方的范围也叫作变量的作用域)中进行的。
在创建变量时,Python将变量名被创建的地点关联给(绑定给)一个特定的命名空间。也就是说在代码中变量创建的位置决定了这个变量将存在于哪个命名空间,也就是它可以被访问的范围。
函数的作用域有助于防止程序之中变量名的冲突,并且有助于函数成为更加独立的程序单元。
1、作用域
变量的作用域可以分为:本地作用域、全局作用域和内置作用域。
在任何情况下,一个变量名的作用域总是由变量在程序中被创建的位置所决定的,并且与函数被调用的地点完全没有关系。
- 如果一个变量在函数内创建,它被定位在这个函数之内,那么他的作用于就是本地的。
- 如果一个变量在一个嵌套的函数内创建,对于外层的函数来说,它是非本地的(也就是外层函数式无法访问的)。
- 如果一个变量在函数之外(也就是在Python的顶级代码快中)创建,它他的作用域就是全局的。
(1) 本地作用域
在一个函数内部对一个变量名(不包含变量成员的引用,例如:name[1]等)任何类型的赋值(而不是在一个表达式中对其进行引用)都会创建新的变量,并把新创建的变量划定为本地的作用域。这包括 = 语句、import 语句、def 语句、函数参数名称等。
在默认的情况下,函数内创建的所有变量都是与函数的本地命名空间相关联的。这意味着:一个在 def 内定义的变量能够被 def 内的代码使用,不能在函数的外部被引用。当在函数之外给一个变量名赋值时(也就是,在一个模块文件的顶层,或者是在交互提示模式下),本地作用域与全局作用域(这个模块的命名空间)是相同的。
本地变量作为临时的变量名,只有在函数运行时才需要他们。
嵌套作用域
Python 为嵌套函数提供了嵌套的命名空间(作用域),使得嵌套在内部的函数内创建的变量名本地化,以便嵌套函数内部使用的变量名不会与嵌套函数外的变量名产生冲突。闭合函数
嵌套作用域的查找在嵌套的函数已经返回后也是有效的。这种行为有时也叫作闭合(closure)函数 —— 一个能够记住嵌套作用域的变量值的函数,尽管那个作用域或许已经不存在了。这是一种相当高级的技术,除了那些拥有函数式编程背景的程序员们,以后在实际使用中并不常见。另一方面,嵌套的作用域常常被 lambda 函数创建表达式使用——因为他们是表达式,它们几乎总是嵌套在一个函数中。此外,函数嵌套通常用作装饰器 —— 在某些情况下,它是最合理的编码模式。
通常来说,类是一个更好的像这样“记忆”的选择,因为它们让状态变得很明确。不使用类的话,全局变量、像这样的嵌套作用域引用以及默认的参数就是 Python 的函数能够保留状态信息的主要方法了。
在 Python 中作用域是可以做任意的嵌套的,但是我们应当尽量少的定义嵌套函数。
(2)全局作用域
函数定义了本地作用域,而模块定义的是全局作用域。每个模块都是一个全局作用域。
全局作用域的作用范围仅限于单个文件。这里的全局指的是在一个文件的顶层创建的变量名仅对于这个文件内部的代码而言是全局的。在 Python 中没有基于一个单个文件的并可以应用在任何其他文件的全局作用域。
全局变量的一个特征是除非被删除掉,否则它们将存活到文件运行结束,且对于所有的函数,他们的值都是可以被访问的。然而局部变量,就像它们存放的栈,只是在函数调用时暂时地存在,仅仅只依赖于定义它们的函数现阶段是否处于活动状态。
(3)内置作用域
内置作用域仅仅是一个名为 builtins 的内置模块,必须要 import builtins 之后才能使用这个模块,因为变量名 builtins 本身并没有预先导入。上面图片中列表中的变量名组成了 Python 中的内置作用域。前一半是内置的异常,后一半是内置函数。由于下面要讲的 LEGB 法则最后将自动搜索这个模块,将会自动得到这个列表中的所有变量。所以你能够使用这些变量而不需要导入 builtins 模块。
2、LEGB法则
Python的变量名解析机制称为 LEGB 法则,这也是由作用域的命名而来的。
当在函数中使用变量时,Python搜索4个 作用域:本地作用域(L)、之后是上一层结构中的 def 或 lambda 的本地作用域(E),之后是全局作用域(G),最后是内置作用域(B)。并且在第一处能够找到这个变量名的地方停下来。如果变量名在这次搜索中没有找到,Python 会抛出 NameError 异常。3、global 语句
在默认情况下,所有在一个函数中被赋值的变量都位于这个函数的本地作用域,并且仅在这个函数运行的过程中存在。为了在函数内创建或修改一个全局作用域的变量,需要使用 global 语句来声明使用全局作用域。
global 语句是一个使用全局命名空间的声明,它告诉 Python 函数打算生成或修改一个或多个全局变量名。global 使得作用域查找跳过本地作用域从全局作用域开始,如果变量不存在将继续到内置作用域。但是,对变量的赋值总是在全局作用域中创建或修改它们。
global 语句包含了关键字 global,其后跟着一个或多个由逗号分开的变量名。当函数主体调用时,所有列出来的变量名将被映射到全局作用域内。最小化全局变量
在默认情况下,函数内部注册的变量名是本地变量,这是有意而为之的。将其改为全局变量会引发一些软件问题:由于变量的值取决于函数调用的顺序,而函数自身是任意顺序进行排列的,导致了程序调试起来变得很困难。另一方面,不使用面向对象的编程方法以及类的话,全局变量也许就是Python中最直接保持全局状态信息的方法(函数在下次被调用时需记住的信息):本地变量在函数返回时将会消失,而全局变量不是这样。
在不熟悉编程的情况下,最好尽可能的避免使用全局变量。
最小化文件间的修改
尽管我们能够直接修改另一个文件中的变量,但是往往我们都不这样做。一个模块文件的全局变量一旦被导入就成为了这个模块对象的一个属性:导入者自动得到了这个被导入的模块文件的所有全局变量的访问权。
这会让两个文件有过强的相关性:假使它们都与变量X的值相关,如果没有其中一个文件的话很难理解或重用另一个文件。这样隐含的跨文件依赖性,在最好的情况下会导致代码不灵活,最坏的情况会引发 bug。
最好的解决办法就是别这样做:在文件间进行通信最好的办法就是通过调用函数,传递参数,然后得到其返回值。
4、nonlocal 语句
如果需要在嵌套函数的内层函数中直接使用外层函数中的变量,可以使用 nonlocal 语句来做到。
nonlocal 应用于嵌套内层的函数作用域中的变量名,而且在声明 nonlocal 名称的时候,他声明的变量必须已经存在于外层嵌套函数的作用域中 。这就允许封闭的函数作为保留状态的一个地方——当一个函数调用的时候,信息被记住了——而不必使用共享的全局名称。
nonlocal 语句还加快了引用——就像 global 语句一样,nonlocal 使得对该语句中列出的名称的查找从嵌套的外层函数的作用域中开始,而不是从所在函数的本地作用域开始。也就是说,nonlocal 名称只能出现在嵌套外层函数中,作用域查找不会继续到全局作用域或内置作用域。
当执行一条 nonlocal 语句时,nonlocal 名称必须已经在一个嵌套的外层函数的作用域中创建,否则将会得到一个错误——不能通过在嵌套的内层函数的作用域中赋给它们一个新值来创建它们。
《Python基础手册》系列:
Python基础手册 1 —— Python语言介绍
Python基础手册 2 —— Python 环境搭建(Linux)
Python基础手册 3 —— Python解释器
Python基础手册 4 —— 文本结构
Python基础手册 5 —— 标识符和关键字
Python基础手册 6 —— 操作符
Python基础手册 7 —— 内建函数
Python基础手册 8 —— Python对象
Python基础手册 9 —— 数字类型
Python基础手册10 —— 序列(字符串)
Python基础手册11 —— 序列(元组&列表)
Python基础手册12 —— 序列(类型操作)
Python基础手册13 —— 映射(字典)
Python基础手册14 —— 集合
Python基础手册15 —— 解析
Python基础手册16 —— 文件
Python基础手册17 —— 简单语句
Python基础手册18 —— 复合语句(流程控制语句)
Python基础手册19 —— 迭代器
Python基础手册20 —— 生成器
Python基础手册21 —— 函数的定义
Python基础手册22 —— 函数的参数
Python基础手册23 —— 函数的调用
Python基础手册24 —— 函数中变量的作用域
Python基础手册25 —— 装饰器
Python基础手册26 —— 错误 & 异常
Python基础手册27 —— 模块
Python基础手册28 —— 模块的高级概念
Python基础手册29 —— 包