作用域规则之变量

命名空间是Python程序运行的一个重要概念,变量只有在具体的命名空间中才有实际的意义。犹如图书馆里的书架编号,01001书架只有在具体的图书管理区域内才能标识实际的位置。老规矩,言谈不是我的长处,代码演示更能表达我的观点。

输出结果: 3 和 5, 未报错

输出结果:3 和 5, 未报错

输出结果:3 和 1, 未报错

输出结果:报错, 未绑定变量 'b'

这是怎么回事呢,接下来我们从Python源码的角度结合运行时信息来探索一下Python变量作用域规则的奥秘。

一般来说,在Python内部,每个独立的代码段都有属于自己的独立的命名空间,模块、类、函数都有独立的命名空间。在第一和第二个示例中,变量b在模块的命名空间中(每个py文件可视为一个模块),而a则在func 代码段的空间中,那么变量b又是怎么被func函数识别呢?

Python源代码被执行,首先要先编译成字节码,然后生成Python运行时栈帧环境,那么接下来就看一下他们字节码和栈帧环境是什么样的。

第一和第二个程序对应栈帧环境几乎是一样的,在func内部打印变量b时,使用的是LOAD_GLOBAL,说明变量b对于func作用域来说是一个全局变量,全局变量与在在源码中的定义位置无关。

第三段程序在打印变量b的时候,使用的是LOAD_FAST字节码,说明b对于func作用来说是个内部的局部变量。

第四段程序也是把变量b作为局部变量来处理的,只是变量b和对应值1之间的约束关系在print语句之后才绑定的,所以就出现了UnboundLocalError异常。

为什么作为全局变量b,可以在模块的任意位置定义,而局部变量b在func内部和定义位置顺序相关呢?

Python的运行时栈帧环境通过PyFrameObject对象来模拟的,定义如下:

对于func来说,全局变量都保存在f_global中,而它是一个PyDictObject对象,也就是字典对象。恍然大悟。。。原来字典本来就是无序的。Python程序运行的过程是先经过编译,然后才执行,所以全局变量定义与位置顺序无关。

为什么第四段程序会报错而第三段没有问题呢,从表面上来看,似乎和定义的先后顺序有关,那到底是怎么相关的呢。通过查看Python的栈帧运行时信息,原来秘密藏在f_localsplus中,被定义为一个长度为1的指针数组(如果长度大于1会自动分配内存)。

我们的都知道,Python是完全面向对象的,也就是说你所能看到的所有的东西都是对象,函数也不例外。func在内部也是一个对象是一个PyFunctionObject,在生成运行时栈帧的过程中,需要初始化参数和局部变量,而这些信息对于函数对象来说都是已知的确定,所以Python在实现对局部变量存取的时候采用了列表的形式,这样可以提高Python运行效率。f_localsplus保存局部变量的规则:参数和位置参数按顺序放在前面,扩展参数紧随其后。f_localsplus包含四部分内容,通过偏移量进行取值。

extras = f->f_code->co_stacksize + f->f_code->co_nlocals +

ncells + nfrees;

通过第四段程序的栈帧字节码可以看出,变量b取值在前,而约束绑定(赋值)在后,就报出了UnBoundLocalError的错误。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180627G0LIG100?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券