
3.2 名称空间
案例65 获取全局名称空间的字典
导语
在Python中,变量以及它所引用的对象都以字典格式存储在内存中,并且由Python进程动态维护。当新的变量产生时会实时地将此变量作为新记录添加到字典中;当变量被销毁(使用del语句删除变量或应用程序退出)后会从字典中将此变量记录删除。
依据名称空间的范围,Python程序在运行后会产生两种类型的名称空间字典——全局字典与局部字典。在模块级别定义的所有变量(标识符)都会存放到全局字典中,而局部字典常存在于函数或者类的内部——即存放函数(或类)内部所定义的变量。
globals函数可以获取存放全局变量的字典引用。

不过,以迭代方式访问字典元素时会发生错误,例如for循环。

在对字典进行迭代的过程中,由于for语句产生了新的变量name和value,存放全局变量的字典被更新,从而引发错误。解决方法有以下3种。
第一种方法是调用字典案例的copy方法复制出一个新的字典案例,因为这个案例不是globals函数所返回的案例,在全局变量发生变更时不会被更新,所以能顺利完成for循环。代码如下:

第二种方法是使用推导语句来从原来的字典案例中产生新的字典案例,因为执行推导语句不会在全局名称空间中出现新的变量。

第三种方法与推导句式类似,即通过对原字典对象进行解包操作,然后再组成一个新的字典案例。字典案例的解包运算符是两个星号(∗∗)。

操作流程
步骤1:声明四个变量并初始化。

步骤2:定义两个函数。

步骤3:定义一个类。

步骤4:获取存放全局变量的字典的引用。

步骤5:使用推导句式重新构造一个字典案例,并且过滤掉名称中以下画线开头的变量。

推导语句可以将for语句与if语句串联使用。首先以for语句从原来的字典中提取一个子项,然后再用if语句后的条件验证一下变量名是否以下画线开头,如果不以下画线开头就把key和value组成“键/值”对添加到新的字典案例中。
步骤6:在屏幕上输出变量名和变量值。

步骤7:运行案例代码,输出结果如下:

注意:不管是在模块级别调用globals函数,还是在函数(或类)内部调用globals函数,该函数始终返回全局名称空间的字典。
案例66 获取局部名称空间的字典
导语
locals函数的功能(与globals函数相对)是返回局部名称空间中存放变量的字典案例。当locals函数在模块级别调用时,它返回的内容与globals函数相同。
locals函数一般在函数或者类的内部调用,以获取局部的字典案例引用。例如下面代码定义了一个work函数,在函数内声明了三个变量,然后调用locals函数获取存放这些变量的字典案例。

存放局部变量的字典与全局字典一样,会实时更新,因此,避免在原来的字典案例上直接使用for循环,需要复制一个新的案例再进行迭代运算。
在类(class)内部调用locals函数所返回的字典案例中,只会包含用户代码中显式声明过的变量(不包括__module__、__qualname__等特殊成员),动态属性以及存储动态属性的__dict__成员也没有包含在字典中。
操作流程
步骤1:定义test函数,并在函数内部声明四个变量和一个嵌套函数。

声明完变量和函数后,调用locals函数获取存放变量的字典案例,接着输出字典中的内容。
步骤2:尝试调用test函数。

步骤3:调用test函数后,输出以下信息:

步骤4:定义一个demo类,在类中声明两个数据成员和一个案例方法。

从locals函数返回的结果中获取的字典案例由_dic成员引用,以便在类外部进行访问。
步骤5:案例化demo类,并设置两个动态属性(a和b)。

步骤6:使用for循环输出_dic成员中的子项。

输出结果如下:

可以看出,动态属性a、b并没有存放到局部名称空间的字典中(动态属性存放到__dict__成员中,它也是字典结构),而且,连存放动态属性的__dict__成员也没有被添加到局部名称空间的字典中。除了特殊成员__module__和__qualname__外,局部字典中只包含代码中明确声明过的m_1、m_2、do_something三个成员。
案例67 直接更新名称空间字典
导语
由于Python应用程序是通过动态维护全局或局部字典案例来存储变量的,因此,直接修改字典案例来管理变量也是可行的。尽管这种做法在代码阅读上显得不太友好,但有时候还是有实用价值的——例如以编程方式动态生成变量列表。
本案例将以编程方式向全局变量字典添加三条记录,实际效果会产生三个新的变量,随后可以在代码中直接访问这些变量。
操作流程
步骤1:获取存储全局变量的字典案例引用。

步骤2:向字典案例添加三条记录。

此举相当于声明了三个变量var_x、var_y和var_z,并完成初始化。
步骤3:将三个变量的值打印到屏幕上。

从代码上看,这三个变量似乎未经过声明就访问了,但它们确实已经存在于全局变量字典中,因此在运行阶段是能够被访问的。
步骤4:运行案例代码,屏幕输出内容如下:

案例68 使用global关键字声明变量
导语
Python的名称空间存在全局与局部两个范围,每个范围都有存在独立的字典案例来管理变量。不同名称空间下的变量字典具有相对独立性,比较典型的问题如下面代码所示。

在change函数中向ch变量赋值字符串“cd”,我们期望的输出结果是:

然而上述代码实际输出结果是:

也就是说,change函数调用后,ch变量并没有被更新。
问题的根源是模块级别声明的ch与change函数内部声明的ch不是同一个变量,尽管它们的名称相同,但它们分属不同的名称空间范围。模块级别的ch变量存储在全局变量字典中,而change函数内的ch变量只存储于函数内的局部变量字典中。
此现象是因Python语言的动态特性而产生的。在Python代码中声明变量不需要指定类型,所以赋值语句直接完成变量的声明。若要让函数内部的ch变量与模块中的ch变量合为同一个变量,那么在change函数中要先用global关键字声明一下变量,然后再赋值。即

这时,change函数内部的变量字典中不会添加ch变量的记录,ch='cd'修改的是模块级别的ch变量。在change函数调用之后,ch变量的值就会变成“cd”。
不过,如果不修改变量的值(仅仅是读取),此种情况下,函数内部是不需要使用global关键字的。例如:

output函数内部只是读取变量的值,没有使用赋值语句,就不会产生新的变量,所以它所访问的就是全局的label变量。
操作流程
步骤1:在代码模块中声明number变量,初始化为10。

步骤2:定义两个函数,依次将number变量的值加上20和30。

为了能顺利修改全局变量number的值,在函数内部需要使用global关键字再次声明number变量,以表明修改的是全局变量而不是声明局部变量。
步骤3:依次调用上述两个函数。

步骤4:打印number变量的值。

步骤5:运行案例代码,输出结果如下:

案例69 使用nonlocal关键字声明变量
导语
nonlocal关键字的用法与global关键字相似,但nonlocal关键字一般用于嵌套函数中(即函数内包含函数)。例如:

nvar变量是在test函数中声明的,嵌套的inner函数试图将nvar变量的值修改为5。但调用test函数后,返回的值仍然是1,而不是5。原因在于inner函数中声明了新的局部变量nvar,被赋值的是这个局部变量而不是test函数中的nvar变量。解决方法是在inner函数中用nonlocal关键字声明nvar变量。

操作流程
步骤1:定义一个somework函数,在函数内声明变量a,并以字符串案例初始化。然后在此函数内部定义两个函数——inner1和inner2,分别修改变量a的值。接着调用inner1和inner2两个函数,再把变量a的值返回。

为了能让嵌套的函数内部可以顺利修改变量a的值,需要使用nonlocal关键字进行一次声明。
步骤2:调用somework函数,并打印它返回的内容。

步骤3:运行案例代码,屏幕输出结果如下:
