返回介绍

第17章 作用域

发布于 2024-01-29 22:24:16 字数 2890 浏览 0 评论 0 收藏 0

第16章介绍了函数定义和调用。正如我们所知,Python的基本函数模型是易用的。这一章将深入介绍Python作用域(变量定义以及查找的地方)以及参数传递(传递给函数作为其输入对象的方式)背后的细节。我们将会看到,在代码中的何处给一个名字赋值,对于确定这个名字的含义很关键。我们还将看到,作用域的用法会对程序维护工作有着重要的影响,例如,过度地使用全局作用域通常是糟糕的事情。

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文