Python 命名空间和作用域介绍

2020-02-24 language python

在 Python 中,一切皆对象,命名空间实际是一个名称到对象的映射,另外,虽然 Python 是一种动态类型的语言,但是静态作用域语言,变量使用与作用域相关。

这里详细介绍命名空间和作用域的关系。

简介

在 Python 中,一切皆对象,而命名空间实际是一个名称到对象 (Objects) 的映射关系 (A namespace is a mapping from names to objects),多数的命名空间是用字典实现的,键就是变量名,值是那些变量的对象,也即变量的值。

另外,Python 是一种动态类型的语言,在编译阶段类型并没有与变量绑定,严格来说,变量名是与具体的变量绑定的。尽管是一种动态语言,但 Python 是静态作用域语言,也就是说,变量的作用域是由它在源代码中的位置决定的。

在使用一个变量之前不必先声明它,但是在真正使用它之前,必须已经绑定到某个对象;而名字绑定将在当前作用域中引入新的变量,同时屏蔽外层作用域中的同名变量,不论这个名字绑定发生在当前作用域中的哪个位置。

命名空间

命名空间包含了当前定义的符号名称及其所引用对象的集合,可以将命名空间看作字典,其中键是对象名称,而值就是对象本身。

在 Python 程序中的任何一个地方,都存在如下几个可用的命名空间:

  • 局部命名空间,每个函数所有,包括了函数内定义的变量、参数;当函数被调用时创建一个局部命名空间,当函数返回结果或抛出异常时被删除;可以通过 locals() 函数访问。
  • 全局命名空间,模块中定义,包括了模块内定义的函数、类、其它导入的模块、模块级的变量和常量,在模块导入时创建,会一直保存到解释器退出;可以通过 globals() 函数访问。
  • 内置命名空间,在 Python 解析器启动时创建,会一直保留,任何模块均可访问,包含了一些常见的函数 (例如 sin()map()open() 等),异常 (例如 BaseExceptionIOError 等);在 Python3 中可以通过 dir(builtins) 命令查看。

通过命名空间保存了变量名和值之间的映射关系,而变量的查找则与作用域相关。

作用域

变量的查询顺序为 LEGB ,也就是局部作用域 (Local),嵌套作用域 (Enclosing),全局作用域 (Global),内置作用域 (Build-in),在查询的过程中如果找到则停止搜索,否则抛出 NameError: name 'xxx' is not defined. 错误。

另外,在早些时候,Python 的是按照 LGB 查找的,后来由于闭包和嵌套函数的出现,于是又增加了嵌套作用域。

示例

内置作用域一般不建议修改 (实际可以通过 builtins 模块修改),可以通过如下的示例验证 LEG 的查找顺序。

float = "global scope"         # <3>
def foobar():
	float = "enclosing scope"  # <2>
	def bar():
		float = "local scope"  # <1>
		print(float)
	bar()
foobar()

直接运行会输出 local scope ,当注释掉 <1> 行后,会显示 enclosing scope ,再注释掉 <2> 之后,会显示 global scope ,如果再注释掉 <3> 行,那么就会输出内建的 float 对象,也就是 <class 'float'>