(以下内容,均基于python3)

最近在看python函数部分,讲到了python的作用域问题,然后又讲了Python的闭包问题。

在做作业的时候,我遇到了几个问题,下面先来看作业。

一、

作业1:

代码A:

 def outside():
var = 5
def inside():
var = 3
print(var) inside()
outside()

代码B:

 def outside():
var = 5
def inside():
print(var)
var = 3 inside()
outside()

代码A结果:3

代码B的结果:UnboundLocalError: local variable 'var' referenced before assignment

本地变量“var”在被赋值之前,就被引用了

作业2:

 def outside():
var = 5 def inside(var):
print(var)
var += 1 inside(var)
print(var) outside()

结果:

5

5

作业3:

 def outside():
var = [1, 2, 3] def inside(new_var):
print(new_var)
new_var[0] = 8 inside(var)
print(var) outside()

结果:

[1, 2, 3]

[8, 2, 3]

作业4:

 1 def outside():
2 var = [1, 2, 3]
3
4 def inside():
5 print(var)
6 var[0] = 8
7
8 inside()
9 print(var)
10
11 out()

结果:

[1, 2, 3]

[8, 2, 3]

作业5:

 def outside():
var = [1, 2, 3]
def inside(): print(var)
var = [4, 5, 6] inside()
print(var)
outside()

结果:UnboundLocalError: local variable 'var' referenced before assignment

本地变量“var”在被赋值之前,就被引用了

我当时,对这4个作业比较迷。。。然后,我就研究了一下,发现问题本质是:1.python变量的作用域问题  2.python的函数传递参数是传值or传引用

要想搞清楚“闭包”,就要搞清楚“作用域”,要想搞清楚“作用域”,就要搞清楚“命名空间”

关系如下:

闭包——>作用域——>命名空间

二、python变量的作用域问题

  1.命名空间:

    1.1什么是命名空间?

      Namespace命名空间,也称名字空间,是从名字到对象的映射。

      命名空间的一大作用是避免名字冲突      

1 def fun1():
2 i = 1
3
4 def fun2():
5 i = 2

      同一个模块中的两个函数中,两个同名名字i之间绝没有任何关系,因为它们分属于不同明明空间。

      如果还不清楚,就打个比方:

        A名字叫作“张三”;然后,B名字也叫作“张三”。当AB俩人分别在各自的家里的时候,如果有人呼唤“张三”这个名字,A和B都知道叫的是自己,而A和B两个人除了名字相同,都叫做“张三”以外,两个人没有任何关系。如果A和B后来,上学了,在同一个班级,那么当老师叫“张三”这个名字的时候,AB就分不清到底叫的是不是自己了。

        这里面,AB这两个人就是变量;“张三”就是变量名;A的家族、B的家族、学校,就是三个不同的命名空间。

      名字就是一个指代和引用,目的是:通过提到“名字”,我们能方便快速地找到“名字”主人的本体,当名字发生冲突时,指代发生混乱,命名空间可以帮我们避免这种冲突。

    1.2命名空间的分类:

      命名空间分3类:内置命名空间、全局命名空间、局部命名空间。

      (1)内置命名空间:built-in名字集合,包括像abs()这样的函数,以及内置的异常名字等。通常,使用内置这个词表示这个命名空间-内置命名空间。

      (2)全局命名空间:模块全局名字集合,直接定义在模块中的名字,如类,函数,导入的其他模块等。通常,使用全局命名空间表示。

      (3)局部命名空间:函数调用过程中的名字集合,函数中的参数,函数体定义的名字等,在函数调用时被“激活”,构成了一个命名空间。通常,使用局部命名空间表示。

      

      注意:1.一个对象的属性集合,也构成了一个命名空间。但通常使用objname.attrname的间接方式访问属性,而不是直接访问,故不将其列入命名空间讨论。

         2.类定义的命名空间,通常解释器进入类定义时,即执行到class ClassName:语句,会新建一个命名空间。(见官方对类定义的说明)

    

    1.3命名空间的生命周期:

      (1)内置命名空间,在Python解释器启动时创建,解释器退出时销毁;

      (2)全局命名空间,模块的全局命名空间在模块定义被解释器读入时创建,解释器退出时销毁;

      (3)局部命名空间,这里要区分函数以及类定义。函数的局部命名空间,在函数调用时创建,函数返回或者由未捕获的异常时销毁;类定义的命名空间,在解释器读到类定义创建,类定义结束后销毁。(关于类定义的命名空间,在类定义结束后销毁,但其实类对象就是这个命名空间内容的包装,见官方对类定义的说明)

  

  2.作用域:

    2.1什么是作用域?

      作用域,这个词听起来很高大上,我觉得,就是一块代码(区域)

      

      (1)变量分为两种引用方式:

        • 直接引用:直接使用名字访问的方式,如name这种方式尝试在名字空间中搜索名字name
        • 间接引用:使用形如objname.attrname的方式,即属性引用,这种方式不会在命名空间中搜索名字attrname,而是搜索名字objname,再访问其属性。   

    2.2作用域与命名空间是什么关系?

       名字作用域就是名字可以影响到的代码文本区域,命名空间的作用域就是这个命名空间可以影响到的代码文本区域。那么也存在这样一个代码文本区域,多个命名空间可以影响到它。 

       运行时的作用域,是按照特定层次组合起来的命名空间。

    2.3名字的搜索规则:

       如果,python程序中,引用了一个名字,python怎样搜索这个名字呢?

        • Local :首先搜索,包含局部名字的最内层(innermost)作用域,如函数/方法/类的内部局部作用域;
        • Enclosing:根据嵌套层次从内到外搜索,包含非局部(nonlocal)非全局(nonglobal)名字的任意封闭函数的作用域。如两个嵌套的函数,内层函数的作用域是局部作用域,外层函数作用域就是内层函数的 Enclosing作用域;
        • Global:倒数第二次被搜索,包含当前模块全局名字的作用域;
        • Built-in:最后被搜索,包含内建名字的最外层作用域。

      Python按照以上L-E-G-B的顺序依次在四个作用域搜索名字。没有搜索到时,Python抛出NameError异常。

      总结:作用域优先级:L-E-G-B

    

    2.4现在再回头来,上面的作业:

    作业1:代码A   

1 def outside():
2 var = 5
3 def inside():
4 var = 3
5 print(var)
6
7 inside()
8 outside()

     结果是:3

    代码运行顺序:8-1-2-7-3-4-5

    8:当Python解释器运行第8行代码时,调用outside()函数;

    1:运行第1行,创建了outside的局部作用域Local(outside);

    2:运行第2行;

    7:运行第7行,调用inside()函数,跳转到第3行;

    3:运行第三行,创建inside的局部作用域Local(inside)。

      此时,在inside()函数内部:由于函数嵌套,outside()是inside()的外层函数,inside()是最里层函数,所以,Local(inside)是局部作用域,Local(outside)是Local(inside)的Enclosing作用域。

      所以,名字的搜索顺序为:Local(inside)——Local(outside)——Global——Built-in

    4:运行第4行,在Local(inside)内部创建一个变量var。

    5:运行第5行,print(var),根据Local(inside)——Local(outside)——Global——Built-in的顺序,搜索名字为var的变量

    所以,输出3,而不是5。

    7:inside()函数执行完毕,返回第7行,Local(inside)作用域销毁。

    8:outside()函数执行完毕,返回第8行,Local(outside)作用域销毁。

   

    代码B:

1 def outside():
2 var = 5
3 def inside():
4 print(var)
5 var = 3
6
7 inside()
8 outside()

    结果:UnboundLocalError: local variable 'var' referenced before assignment

本地变量“var”在被赋值之前,就被引用了

      代码执行顺序:8-1-2-7-3-4-5

      8-1-2-7-3:python执行,同代码A

      3:运行第3行,创建Local(inside)局部作用域

      4:python解释器运行第4行,print(var),从Local根据Local(inside)——Local(outside)——Global——Built-in的顺序,搜索名字为var的变量。

        发现,在最内部局部作用域Local(inside),无法找到名字为“var”的变量!

        然后,python解释器,会继续执行完inside()这个函数!!!

        在第5行,发现赋值语句:var = 3

        则,python解释器就认为,var这个变量是属于Local(inside)局部作用域的。

        既然对变量 b 的赋值(声明)发生在 print 语句之后, print 语句执行时,变量 b 是还未被声明的,于是抛出错误:变量在赋值前就被引用。

    代码2:

 1 def outside():
2 var = 5
3
4 def inside(var):
5 print(var)
6 var += 1
7
8 inside(var)
9 print(var)
10
11 outside()

    结果:

    5

    5

    原因:

    第4行,内部函数声明了形参var,在Local(inside)局部作用域内部,创建了一个新的名为“var”的变量

    第8行,通过调用函数inside(var),传参,将Local(outside)作用域的变量var的值5,传递给内部的Local(outside)作用域的变量var,使它的初始值也为5。

    第5行,打印变量var,得5

    第6行,var += 1,内部函数(作用域Local(inside))的变量var的值,变为6。

    返回第8行,inside()函数执行完毕,内部作用域Local(inside)随之销毁。

    第9行,名字搜索顺序为:Local(outside)——Global——Built-in,所以输出打印outside()作用域中的变量var的值,5.

python命名空间、作用域、闭包与传值传引用的更多相关文章

  1. python基础(7)-函数&命名空间&作用域&闭包

    函数 动态参数 *args def sum(*args): ''' 任何参数都会被args以元组的方式接收 ''' print(type(args)) # result:<class 'tupl ...

  2. [Python] 命名空间&作用域

    Python的类语句不会创建实例 类会创建命名空间,通过对象访问类的属性和方法 类不会创建作用域,对方法和属性的引用必须加以限定(如在方法中必须通过self引用实例的属性) class My1(): ...

  3. python中的类变量和对象变量,以及传值传引用的探究

    http://www.cnblogs.com/gtarcoder/p/5005897.html http://www.cnblogs.com/mexh/p/9967811.html

  4. python变量作用域,函数与传参

    一.元组传值: 一般情况下函数传递参数是1对1,这里x,y是2个参数,按道理要传2个参数,如果直接传递元祖,其实是传递一个参数 >>> def show( x, y ): ... p ...

  5. 成对使用new和delete,传值传引用

    首先: delete []p;是用来删除对象数组的,特别是你声明的是对象数组!!! 如果new中用了[],delete一定要用[]:在new中没有使用,在delete中一定不要使用. 其次: 当你使用 ...

  6. 简谈Java传值传引用

    本随笔旨在强化理解传值与传引用   如下代码的运行结果 其中i没有改变,s也没有改变. 但model中的值均改变了. i :100s :hellomodel :testchangemodel2 :ch ...

  7. python 嵌套作用域 闭包函数

    #闭包函数 def multiplier(factor): def multiplyByFactory(number): return number*factor return multiplyByF ...

  8. python命名空间与闭包函数详解

    python命名空间与闭包函数详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 本篇博客主要介绍的知识点如下: 1>.三元运算 2>.命名空间 3>.globa ...

  9. python—命名空间、作用域查找顺序、闭包

    名称空间 name space,如下图: x = 1, 1存放在内存中,1 会有一个内存地址,x 则 存放在 name space 里,并同时记录了 1的内存地址, 即 名称空间是存放了变量x与1绑定 ...

随机推荐

  1. mac home brew install go

    mac利器home brew安装Go 首先你得需要安装home brew和ruby环境(因为home brew依赖ruby) 如果没有请自行到链接安装 准备好之后就开始安装go了 brew updat ...

  2. test image

    Most of these images are in PBM or PGM format and compressed with GNU Zip and GNU TAR Note: These pa ...

  3. Ajax (Asynchronous javascript xml) 搜索框核心代码(JQuery) Ajax判断用户名存在核心代码 附:原生js的Ajax代码 其中有json的一句话解释

    前端 <script type="text/javascript"> $(function(){ $("#tid").keyup(function( ...

  4. EAIntroView–高度可定制的iOS应用欢迎页通用解决方案

    简介 高度可定制的应用欢迎页通用解决方案,可高度定制,不要仅限于现有的demo. 项目主页: EAIntroView 最新示例: 点击下载 入门 安装 安装后,引入” EAIntroView.h”并设 ...

  5. 关于Vue 兄弟组件通信

    最近项目中遇到希望在操作路由组件里面内容的时候可以影响共用组件Header组件(这个其实就是他的兄弟组件)的操作.  意思就是 router-view指向的router来影响Header组件的信息 首 ...

  6. zabbix服务端安装配置

    1.安装好httpd,mysql,php yum install httpd php mysql mysql-devel php-xmlwriter php-gd php-mbstring php-b ...

  7. Linux下配置npm存放路径,解决权限问题

    1.打开cmd命令行,查看当前配置 输入 npm config ls 先看一下当前npm的配置环境,由于我已经修改过,所以可以看到修改后的路径 2.修改路径 这里需要修改两个路径,module路径和c ...

  8. php 删除指定扩展名文件

    <?php /** *@param $path文件夹绝对路径 $file_type待删除文件的后缀名 *return void */ function clearn_file($path, $f ...

  9. 图解HTTP总结(4)——返回结果的HTTP状态码

    HTTP状态码负责表示客户端HTTP请求的返回结果.标记服务器端的处理是否正常.通知出现的错误等工作. 状态码的类别 2XX 成功 200 OK 表示从客户端发来的请求在服务器端被正常处理了. 在响应 ...

  10. linux系统集群之高可用(一)HA

    HA(High aviliable)高可用 高可用的需求 在很多公司里面,都会存在着一些不愿被中断的业务,但是由于硬件故障,软件故障,人为因素等各种因素,往往会不经意的造成我们重要的业务中断,因此高可 ...