Python基础手册24——函数中变量的作用域

五、变量的作用域

当你在一个程序中使用变量名时,Python创建、改变或查找变量名都是在命名空间(一个保存变量名的地方,这个地方的范围也叫作变量的作用域)中进行的。

在创建变量时,Python将变量名被创建的地点关联给(绑定给)一个特定的命名空间。也就是说在代码中变量创建的位置决定了这个变量将存在于哪个命名空间,也就是它可以被访问的范围。

函数的作用域有助于防止程序之中变量名的冲突,并且有助于函数成为更加独立的程序单元。


1、作用域

变量的作用域可以分为:本地作用域、全局作用域和内置作用域。

在任何情况下,一个变量名的作用域总是由变量在程序中被创建的位置所决定的,并且与函数被调用的地点完全没有关系。

  1. 如果一个变量在函数内创建,它被定位在这个函数之内,那么他的作用于就是本地的。
  2. 如果一个变量在一个嵌套的函数内创建,对于外层的函数来说,它是非本地的(也就是外层函数式无法访问的)。
  3. 如果一个变量在函数之外(也就是在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 —— 包

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容

  • 1、引言 最近在刷leetcode题的时候,遇到一个求最长回文子串的题目,于是,我写了如下的代码: 哎呀,写了两个...
    文哥的学习日记阅读 14,188评论 6 32
  • Python作用域基础 当你在一个程序中适用变量名时,Python创建、改变或查找变量名都是在所谓的命名空间(一个...
    聽風踏雪阅读 334评论 0 0
  • Python作用域基础 当在程序中使用变量名时,Python创建、改变或查找变量名都是在所谓的命名空间中进行的。作...
    So_ProbuING阅读 287评论 0 1
  • 函数和对象 1、函数 1.1 函数概述 函数对于任何一门语言来说都是核心的概念。通过函数可以封装任意多条语句,而且...
    道无虚阅读 4,348评论 0 5
  • 不知道在每个人眼里,根是什么定义,我的理解是生我养我的地方. 不知道从什么时候开始,我们每年回家的次数越来越少,也...
    DAJU阅读 386评论 0 0