- 译者序
- 前言
- 第1章 问答环节
- 第2章 Python 如何运行程序
- 第3章 如何运行程序
- 第4章 介绍 Python 对象类型
- 第5章 数字
- 第6章 动态类型简介
- 第7章 字符串
- 第8章 列表与字典
- 第9章 元组、文件及其他
- 第10章 Python 语句简介
- 第11章 赋值、表达式和打印
- 第12章 if 测试和语法规则
- 第13章 while 和 for 循环
- 第14章 迭代器和解析,第一部分
- 第15章 文档
- 第16章 函数基础
- 第17章 作用域
- 第18章 参数
- 第19章 函数的高级话题
- 第20章 迭代和解析,第二部分
- 第21章 模块:宏伟蓝图
- 第22章 模块代码编写基础
- 第23章 模块包
- 第24章 高级模块话题
- 第25章 OOP:宏伟蓝图
- 第27章 更多实例
- 第28章 类代码编写细节
- 第29章 运算符重载
- 第30章 类的设计
- 第31章 类的高级主题
- 第32章 异常基础
- 第34章 异常对象
- 第35章 异常的设计
- 第36章 Unicode 和字节字符串
- 字符串基础知识
- Python 的字符串类型
- 文本和二进制文件
- Python 3.0 中的字符串应用
- 转换
- 编码 Unicode 字符串
- 编码非ASCII文本
- 编码和解码非ASCII文本
- 其他 Unicode 编码技术
- 转换编码
- 在 Python 2.6 中编码 Unicode 字符串
- 源文件字符集编码声明
- 使用 Python 3.0 Bytes 对象
- 序列操作
- 创建 bytes 对象的其他方式
- 混合字符串类型
- 使用 Python 3.0(和 Python 2.6)bytearray 对象
- 使用文本文件和二进制文件
- Python 3.0 中的文本和二进制模式
- 类型和内容错误匹配
- 使用 Unicode 文件
- 在 Python 3.0 中处理 BOM
- Python 2.6 中的 Unicode 文件
- Python 3.0 中其他字符串工具的变化
- Struct二进制数据模块
- pickle对象序列化模块
- XML解析工具
- 本章小结
- 本章习题
- 习题解答
- 第37章 管理属性
- 第38章 装饰器
- 第39章 元类
- 附录A 安装和配置
- 附录B 各部分练习题的解答
- 作者介绍
- 封面介绍
第17章 作用域
Python作用域基础
既然现在你已经准备编写函数了,那么我们需要更正式地了解Python中变量名的含义。当你在一个程序中使用变量名时,Python创建、改变或查找变量名都是在所谓的命名空间(一个保存变量名的地方)中进行的。当我们谈论到搜索变量名对应于代码的值的时候,作用域这个术语指的就是命名空间。也就是说,在代码中变量名被赋值的位置决定了这个变量名能被访问到的范围。
关于所有变量名,包括作用域的定义在内,都是在Python赋值的时候生成的。正如我们所知,Python中的变量名在第一次赋值时已经创建,并且必须经过赋值后才能够使用。由于变量名最初没有声明,Python将一个变量名被赋值的地点关联为(绑定给)一个特定的命名空间。换句话说,在代码中给一个变量赋值的地方决定了这个变量将存在于哪个命名空间,也就是它可见的范围。
除打包代码之外,函数还为程序增加了一个额外的命名空间层:在默认的情况下,一个函数的所有变量名都是与函数的命名空间相关联的。这意味着:
·一个在def内定义的变量名能够被def内的代码使用。不能在函数的外部引用这样的变量名。
·def之中的变量名与def之外的变量名并不冲突,即使是使用在别处的相同的变量名。一个在def之外被赋值(例如,在另外一个def之中或者在模块文件的顶层)的变量X与在这个def之中的赋值的变量X是完全不同的变量。
在任何情况下,一个变量的作用域(它所使用的地方)总是由在代码中被赋值的地方所决定,并且与函数调用完全没有关系。实际上,正如我们将在本章中学到的,变量可以在3个不同的地方分配,分别对应3种不同的作用域:
·如果一个变量在def内赋值,它被定位在这个函数之内。
·如果一个变量在一个嵌套的def中赋值,对于嵌套的函数来说,它是非本地的。
·如果在def之外赋值,它就是整个文件全局的。
我们将其称为语义作用域,因为变量的作用域完全是由变量在程序文件中源代码的位置而决定的,而不是由函数调用决定。
例如,在下面的模块文件中,X=99这个赋值语句创建了一个名为X的全局变量(在这个文件中可见),但是X=88这个赋值语句创建了一个本地变量X(只是在def语句内是可见的)。
尽管这两个变量名都是X,但是它们作用域可以把它们区别开来。实际上,函数的作用域有助于防止程序之中变量名的冲突,并且有助于函数成为更加独立的程序单元。
作用域法则
在开始编写函数之前,我们编写的所有的代码都是位于一个模块的顶层(也就是说,并不是嵌套在def之中),所以我们使用的变量名要么就是存在于模块文件本身,要么就是Python内置预先定义好的(例如,open)。函数提供了嵌套的命名空间(作用域),使其内部使用的变量名本地化,以便函数内部使用的变量名不会与函数外(在一个模块或是其他的函数中)的变量名产生冲突。再一次说明,函数定义了本地作用域,而模块定义的是全局作用域。这两个作用域有如下的关系。
·内嵌的模块是全局作用域。每个模块都是一个全局作用域(也就是说,一个创建于模块文件顶层的变量的命名空间)。对于外部的全局变量就成为一个模块对象的属性,但是在一个模块中能够像简单的变量一样使用。
·全局作用域的作用范围仅限于单个文件。别被这里的“全局”所迷惑,这里的全局指的是在一个文件的顶层的变量名仅对于这个文件内部的代码而言是全局的。在Python中是没有基于一个单个的、无所不包的情景文件的全局作用域的。替代这种方法的是,变量名由模块文件隔开,并且必须精确地导入一个模块文件才能够使用这个文件中定义的变量名。当你在Python中听到“全局的”,你就应该想到“模块”。
·每次对函数的调用都创建了一个新的本地作用域。每次调用函数,都创建了一个新的本地作用域。也就是说,将会存在由那个函数创建的变量的命名空间。可以认为每一个def语句(以及lambda表达式)都定义了一个新的本地作用域,但是因为Python允许函数在循环中调用自身(一种叫做递归的高级技术),所以从技术上讲,本地作用域实际上对应的是函数的调用。换句话说,每一个函数调用都创建了一个新的本地命名空间。递归在处理不能提前预知的流程结构时是一个有用工具。
·赋值的变量名除非声明为全局变量或非本地变量,否则均为本地变量。在默认情况下,所有函数定义内部的变量名是位于本地作用域(与函数调用相关的)内的。如果需要给一个在函数内部却位于模块文件顶层的变量名赋值,需要在函数内部通过global语句声明。如果需要给位于一个嵌套的def中的名称赋值,从Python 3.0开始可以通过在一条nonlocal语句中声明它来做到。
·所有其他的变量名都可以归纳为本地、全局或者内置的。在函数定义内部的尚未赋值的变量名是一个在一定范围内(在这个def内部)的本地变量、全局(在一个模块的命名空间内部)或者内置(由Python的预定义__builtin__模块提供的)变量。
这里还有一些细节需要注意。首先,记住以交互命令提示模式输入的代码也遵从这些规则。你可能还不知道,但是,交互模式运行的代码实际上真的输入到一个叫做__main__的内置模块中;这个模块就像一个模块文件一样工作,但是,结果随着输入而反馈。因此,交互模式也在一个模块中创建名称,并由此遵守常规的作用域规则:它们对于交互会话来说是全局的。我们将在本书的下一个部分学习有关模块的内容。
还要注意,一个函数内部的任何类型的赋值都会把一个名称划定为本地的。这包括=语句、import中的模块名称、def中的函数名称、函数参数名称等。如果在一个def中以任何方式赋值一个名称,它都将对于该函数成为本地的。
此外,注意原处改变对象并不会把变量划分为本地变量,实际上只有对变量名赋值才可以。例如,如果变量名L在模块的顶层被赋值为一个列表,在函数内部的像L.append(X)这样的语句并不会将L划分为本地变量,而L=X却可以。通常,记住名称和对象之间的清楚的区分是有帮助的:修改一个对象并不是对一个名称赋值。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论