zoukankan      html  css  js  c++  java
  • python命名空间、作用域、闭包与传值传引用

    (以下内容,均基于python3)

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

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

    一、

    作业1:

    代码A:

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

    代码B:

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

    代码A结果:3

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

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

     作业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

    作业3:

     1 def outside():
     2     var = [1, 2, 3]
     3     
     4     def inside(new_var):
     5         print(new_var)
     6         new_var[0] = 8
     7     
     8     inside(var)
     9     print(var)
    10 
    11 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:

     1 def outside():
     2     var = [1, 2, 3]
     3     def inside():
     4 
     5         print(var)
     6         var = [4, 5, 6] 
     7         
     8     inside()
     9     print(var)
    10 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.

     

     

  • 相关阅读:
    ie 火狐兼容集锦
    ie css png
    jQuery插件——autoTextarea-文本框根据输入内容自适应高度
    比onload更快获取图片尺寸(转载)
    数据库性能问题排查
    项目管理_FindBugs的使用
    js动态获取子复选项并设计全选及提交
    SVN使用_获取某版本后改动的文件列表
    存储过程_把字符串转化为结果集
    Spring下如何配置bean
  • 原文地址:https://www.cnblogs.com/tommyngx/p/10361406.html
Copyright © 2011-2022 走看看