下载安卓APP箭头
箭头给我发消息

客服QQ:3315713922
论坛 >编程语言 >Python 的命名空间 (2)

Python 的命名空间 (2)

课课家iOS游客发布于 2018-05-02 09:51查看:1104

global 和 nonlocal 语句

global 和 nonlocal 的作用

如前所述,对于上层变量,python 允许直接读取,但是却不可以在内层作用域直接改写上层变量,来看一个典型的结构:

image.png

上面对函数内的 gv 和 lv 进行赋值操作后,两处都会发生 UnboundLocalError,因为 Python 并不知道你是想在内层作用域生成一个同名的局部变量,还是想直接改写上层变量,因此便会引起错误。为此,Python 引入了 globalnonlocal 语句就来说明所修饰的 gvlv 分别来自全局作用域和父函数作用域,声明之后,就可以在 func 和 inn_func 内直接改写上层作用域内 gv 和 lv 的值:

image.png

image.png

如上,全局变量 gv 值被函数改写了, inn_func 修改的也确实是父函数 lv的值 (依据 ID 判断)。

借壳

那么是不是不使用 global 和 nonlocal 就不能达到上面的目的呢?来看看这段程序:

image.png

执行的结果:

image.png

可以发现,执行结果同上面完全一致,问题自然来了:“为什么不用 global nonlocal 也可以改写全局变量gv和父函数变量lv的值?

为了看清楚这个过程,我们将上面的gv.insert(0, 'gv') lv.append(v) 改写为 gv[0:0] = ['gv'] lv[:] = [v]:

image.png

执行结果:

image.png

同 g.py 文件的执行结果完全一致,事实上两者之间的内在也是完全一样的。
So 我们其实改写的不是 gv 和 lv ,而是 gv 和 lv 的元素 gv[0:0] 和 lv[:] 。因此,不需要 global 和 nonlocal 修饰就可以直接改写,这就是“借壳”,在 nonlocal 尚未引入 Python 中,比如 Python 2.x 若要在子函数中改写父函数变量的值就得通过这种方法。
当然借壳蕴藏着一个相对复杂的标识符创建的问题:比如子函数通过借壳修改父函数变量lv的值,那么子函数的标识符lv是怎么绑定到父函数变量lv的值 ID 的上的?

关于这个问题,这里有个问答就是讨论这个的:

global 和 nonlocal 语句对标识符创建的不同影响

另外,需要注意的是:global 语句只是声明该标识符引用的变量来自于全局变量,但并不能直接在当前层创建该标识符;nonlocal 语句则会在子函数命名空间中创建与父函数变量同名的标识符:

image.png

执行结果:

image.png

之所以 nonlocal 语句与 global 语句的处置不同,在于全局变量在模块内随时都可以访问,而父函数变量在父函数执行完毕后便会释放,因此 nonlocal 语句必须将父函数变量的标识符和引用写入闭包命名空间。

命名空间和标识符的创建

创建规则

实际上,到这里其实还有一个重要的重要问题没有解决:“标识符并不是天生就在命名空间内的,命名空间也不是平白无故就产生的,那么什么时候会创建命名空间和标识符呢?”
规则有三:

  1. 赋值、定义函数和类时产生标识符;

  2. 类和函数定义(def 和 lambda)执行时产生新的命名空间;

  3. 标识符产生地点决定标识符所处的命名空间。

这三点就是拿来秒懂的!不过,仍然有一点常常被忽视:类的命名空间:

类的局部命名空间

首先,函数和类执行时都会产生局部命名空间,但类的执行机制不同于函数:

image.png

执行文件,结果为:

image.png

如上,类就是一个可执行的代码块,只要该类被加载,就会被执行,这一点不同于函数。
类之所以这么设计的原因在于:类是创建其他实例(生成其他的类或者具体的对象)的对象,因此必须在实例之前被创建,而类又可能涉及到与其他类的继承、重载等一系列问题,故在代码加载时就被创建利于提高效率和降低逻辑复杂度。

其次,与函数不同的是,类的局部命名空间并非作用域

image.png

执行上段代码,我们可以发现在类 A 内列表推导式无法调取 a 的值,但函数却可以:

image.png

因此,A 中的 a 不同于函数 func 中的 a 在局部命名空间中可以被任意读取,之所以说是“不可以被任意”读取而不是“不可被读取”,原因在于在类A 的局部空间内,a 其实一定程度上是可以直接被读取的:

image.png

执行上段代码后:

image.png

而上例中 b 的赋值操作不能执行,原因在于列表推导式会创建自己的局部命名空间,因此难以访问到 a

编译与局部命名空间

Python 是动态语言,很多行为是动态发生的,但 Python 自身也在不断进步,比如为了提高效率,有些行为会在编译时候完成,局部变量的创建就是如此:

image.png

上段程序还未执行,就提示存在有语法错误,原因在于python 解释器发现 inn_func 内存在自身的 a 变量,但却在声明之前就被 print 了。

总结

啰嗦了这么多,终于该结尾了!
我们再来回过头来看下文章开头的栗子:
1、为什么 b.py 只是导入 a.py 中的 class A,却执行了整个 a.py 文件?
答:因为 Python 并不知道 class A 在 a.py 文档的何处,为了能够找到 class A,Python 需要执行整个文档。
2、为什么 b.py 的导入执行了整个 a.py 文档,却在 b 中难以调用 a 的全局变量 va
答:Python 的全局变量指的是模块全局,因此不可以跨文档,因此 global 语句也是不可以跨文档的。另外, b 只是导入了 a 的 class A,因此并不会导入 a 中所有的标识符,所以 类似a.va 这样的调用也是不起作用的。

关于命名空间:
1、赋值、定义类和函数都会产生新的标识符;
2、全局变量的标识符不能跨文档;
3、各级命名空间相互独立互不影响;
4、Python 总是从当前层逐渐向上寻找标识符;
5、内层作用域若想直接修改上层变量,需要通过 global nonlocal 语句先声明;
6、单纯的 global 语句并不能为所在层级创建相应标识符,但 nonlocal 语句可以在闭包空间中创建相应标识符;
7、类的局部命名空间不是作用域。



收藏(0)0
查看评分情况

全部评分

此主贴暂时没有点赞评分

总计:0

回复分享

版主推荐

    共有0条评论

      • IT宅男
      • mr jack
      • Mr ken
      • Mright
      • cappuccino
      • YUI
      • 课课家运营团队
      • 课课家技术团队1
      • 酸酸~甜甜
      • 选择版块:

      • 标题:

      • 内容

      • 验证码:

      • 标题:

      • 内容

      • 选择版块:

      移动帖子x

      移动到: