python命名空间和作用域之global和nonlocal初理解

 

一、命名空间:

1.命名空间 是从命名到对象的映射

2.当前命名空间主要是通过 Python 字典实现的,键为变量名,值是变量对应的值。 

3.关于命名空间需要了解的一件很重要的事就是不同命名空间中的命名没有任何联系,例如两个不同的模块可能都会定义一个名为 maximize的函数而不会发生混淆-用户必须以模块名为前缀来引用它们。

4. 一个命名空间中不能有重名,但是不同的命名空间可以重名而没有任何影响。

5.分类

python程序执行期间会有2个或3个活动的命名空间(函数调用时有3个,函数调用结束后2个)。按照变量定义的位置,可以划分为以下3类:

    Local,局部命名空间,每个函数所拥有的命名空间,记录了函数中定义的所有变量,包括函数的入参、内部定义的局部变量。

    Global,全局命名空间,每个模块加载执行时创建的,记录了模块中定义的变量,包括模块中定义的函数、类、其他导入的模块、模块级的变量与常量。

    Built-in,python自带的内建命名空间,任何模块均可以访问,放着内置的函数和异常。

6.生命周期

    Local(局部命名空间)在函数被调用时才被创建,但函数返回结果或抛出异常时被删除。(每一个递归函数都拥有自己的命名空间)。

    Global(全局命名空间)在模块被加载时创建,通常一直保留直到python解释器退出。

    Built-in(内建命名空间)在python解释器启动时创建,一直保留直到解释器退出。

 

各命名空间创建顺序:

python解释器启动 ->创建内建命名空间 -> 加载模块 -> 创建全局命名空间 ->函数被调用 ->创建局部命名空间

各命名空间销毁顺序:

函数调用结束 -> 销毁函数对应的局部命名空间 -> python虚拟机(解释器)退出 ->销毁全局命名空间 ->销毁内建命名空间

    python解释器加载阶段会创建出内建命名空间、模块的全局命名空间,局部命名空间是在运行阶段函数被调用时动态创建出来的,函数调用结束动态的销毁的。

 

二、作用域:

作用域 就是一个 Python 程序可以直接访问命名空间的正文区域。这里的直接访问意思是一个对名称的错误引用会尝试在命名空间内查找。尽管作用域是静态定义,在使用时他们都是动态的。每次执行时,至少有三个命名空间可以直接访问的作用域嵌套在一起:

  • 包含局部命名的使用域在最里面,首先被搜索;其次搜索的是中层的作用域,这里包含了同级的函数;

    最后搜索最外面的作用域,它包含内置命名。

  • 首先搜索最内层的作用域,它包含局部命名任意函数包含的作用域,是内层嵌套作用域搜索起点,包含非局部,但是也非全局的命名

  • 接下来的作用域包含当前模块的全局命名

  • 最外层的作用域(最后搜索)是包含内置命名的命名空间

作用域是针对变量而言,指申明的变量在程序里的可应用范围。或者称为变量的可见性。 

【变量查找法则】简单总结:访问顺序:L->E->G->B 

即 LOCAL(局部作用域)->ENCLOSING(闭包函数外的函数,可理解为局部外的局部)

->GLOBAL(全局作用域)->Built-in (内置作用域)(内置函数所在模块的范围)

 

    在任意一个作用域中找到变量则停止查找。所有作用域查找完成没有找到对应的变量,则抛出 NameError: name 'xxxx' is not defined的异常。

    在局部作用域中,可以看到局部作用域、嵌套作用域、全局作用域、内建作用域中所有定义的变量。

    在全局作用域中,可以看到全局作用域、内建作用域中的所有定义的变量,无法看到局部作用域中的变量。

 

【分类】

    只有函数、类、模块会产生作用域,代码块不会产生作用域(参考代码1)。作用域按照变量的定义位置可以划分为4类:

    Local(函数内部)局部作用域

    Enclosing(嵌套函数的外层函数内部)嵌套作用域(闭包)

    Global(模块全局)全局作用域

    Built-in(内建)内建作用域

 

【规则】

    1、静态作用域规则

         定义:python中变量的作用域是由它在源代码中的位置决定的。(名字查找是动态发生的)

         说明:参考代码3/4,以代码3为例说明,在模块中定义了一个全局变量 i = 1,在test方法中执行 i += 1,对变量 i进行了赋值动作,该赋值动作决定了i在test()方法中是一个局部变量, i += 1可以拆分为两步执行,首先执行 i + 1, 然后将结果赋值给i。执行i + 1操作时,i虽然申明为局部变量,但是没有绑定任何具体值,因此报错。

    2、最内嵌套作用域规则

         定义:由一个赋值语句引进的名字在这个赋值语句所在的作用域里是可见(起作用)的,而且在其内部嵌套的每个作用域内也可见,除非它被嵌套于内部的且引进同样名字的赋值语句所遮蔽。

         说明:参考代码5. 方法g()是方法f()中定义的内嵌函数。在方法f()中定义的局部变量 i =2,在内嵌方法 g()中是可见的。如果在g()中又定义一个重名的变量 i = 3,则f()中定义的变量将被遮蔽。

 

三、命名空间与作用域的关系

    命名空间定义了在某个作用域内变量名和绑定值之间的对应关系,命名空间是键值对的集合,变量名与值是一一对应关系。作用域定义了命名空间中的变量能够在多大范围内起作用。

    命名空间在python解释器中是以字典的形式存在的,是以一种可以看得见摸得着的实体存在的。作用域是python解释器定义的一种规则,该规则确定了运行时变量查找的顺序,是一种形而上的虚的规定。

 

四、一些规则

1.在Python 中,如果没有使用 global 语法,其赋值操作总是在最里层的作用域

2.赋值不会复制数据,只是将命名绑定到对象。删除也是如此del x 只是从局部作用域的命名空间中删除命名 x 。

3.事实上,所有引入新命名的操作都作用于局部作用域。特别是 import 语句和函数定义将模块名或函数绑定于局部作用域(可以使用 global 语句将变量引入到全局作用域)。

4.global 语句用以指明某个特定的变量为全局作用域,并重新绑定它。nonlocal 语句用以指明某个特定的变量为封闭作用域,并重新绑定它。

四、例子说明

def scope_test():
    def do_local():
        spam = "local spam"
    def do_nonlocal():
        nonlocal spam
        spam = "nonlocal spam"
    def do_global():
        global spam
        spam = "global spam"
    spam = "test spam"
    do_local()
    print("After local assignment:", spam)
    do_nonlocal()
    print("After nonlocal assignment:", spam)
    do_global()
    print("After global assignment:", spam)
    #spam="test spam2"
    #print(spam)

scope_test()
print("In global scope:", spam)

输出: 

After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
#test spam2
In global scope: global spam

接下来,让我们一起一步步分析:

首先,spam = "test spam"

          spam绑定的是test spam,作用于scope_test 。

然后,do_local()

           do_local中的spam仅作用于do_local,也没用global或nonlocal改变其作用域,将scope_test中的spam进行重新绑定。

接着,do_nonlocal()

           do_nonlocal使用了nonlocal改变了位于其命名空间中的spam作用域,由局部变为闭包函数外的函数,即由do_nonlocal变为了scope_test 。并通过  spam = "nonlocal spam"  将spam重新绑定了值。即此时do_nonlocal和scope_test 中的spam作用域一样,绑定的值也一样(笔者认为两个spam此时指向相同,合为一体。其实在改变作用域的那一刻两个spam已经是同一个变量)。

再接着,do_global()

            do_global使用global改变了do_global中的spam作用域,但没改变scope_test 中的spam作用域,同样的,do_global只是改变了do_global中作用域为全局的spam所绑定的值,即全局变量spam绑定的值是 "global spam" ,但是闭包函数之外的函数中的spam绑定的值仍然是  "nonlocal spam" ,即scope_test 中的spam绑定的值仍是  "nonlocal spam" 。这就像do_local和scope_test 一样,在全局中,无法调用函数scope_test中的spam,scope_test中的spam也影响不了全局中的spam,(可以把注释那一行去掉运行看看)。

         print("After global assignment:", spam)-->此时根据变量查找规则LEGB,首先查找到的是作为E的闭包函数外的函数中的变量spam,而不是作为G的全局变量spam,而查找只要查找到了一个结果就会停止接下去的查找,故打印出来的是 "nonlocal_spam"。而在最后全局中调用打印spam时,由于scope_test中的spam只作用于scope_test函数中,故查找到的只有作为全局变量的spam,其绑定的值就是"global spam"。

 

部分来源于:https://www.cnblogs.com/zhangxinhe/p/6963462.html

http://www.runoob.com/python3/python3-function.html

https://docs.pythontab.com/python/python3.5/classes.html#tut-scopeexample

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章