Python 的命名空间

498 查看

懒得扫全文的童鞋,可以直接跳到最后看总结。
我们先从一个简单的栗子说起:

栗子

a 文件中有变量 va 以及类 A,b 文件导入 aclass A ,并打印出 A

执行 b 文件的结果为:

可以发现,虽然 b 只是导入了 a 中的 class A,但导入这个过程却执行了整个 a 文件,那么我们是否能够在 b 中访问 a 中的全局变量 va 呢:

尝试了各类调用方法,发现都无法正常访问 a 的全局变量 va,既然 b 的导入执行了整个 a 文件,甚至还打印出了 vaid 和值,又为什么无法在 b 中调用 va 呢?

这个问题所涉及到的内容就是:命名空间。

但在开始正题之前,我们需要阐明若干概念:

一些基本概念的澄清

对象

Python 一切皆对象,每个对象都具有 一个ID、一个类型、一个值;对象一旦建立,ID 便不会改变,可以直观的认为 ID 就是对象在内存中的地址

上例 a, b 共享了同一个 ID、同一个值、同一个类型。因此 a, b 表达的是同一个对象,但 a, b 又明显是不同的,比如一个叫 'a' 一个叫 'b'…既然是同一个对象,为什么又有不同的名字呢?难道名字不是对象的属性?

标识符

事实确实如此,这是 Python 比较特殊一点:如同'a' 'b' 这样的名称其实有一个共同的名字:identifier(注意不要与 ID 混淆了),中文名为“标识符”,来解释一下:

标识符:各类对象的名称,比如函数名、方法名、类名,变量名、常量名等。

在 Python 中赋值并不会直接复制数据,而只是将名称绑定到对象,对象本身是不知道也不需要关心(该关心这个的是程序猿)自己叫什么名字的。一个对象甚至可以指向不同的标识符,上例中的'a' 'b'便是如此。真正管理这些名子的事物就是本文的主角”命名空间” 。

命名空间

命名空间:(英语:Namespace)表示标识符(identifier)的可见范围。(ps:copy 自 SF)

简而言之,命名空间可以被简单的理解为:存放和使用对象名字的抽象空间

作用域

与命名空间相对的一个概念就是“作用域”,那么什么又是作用域呢?

作用域:(英文 Scope)是可以直接访问到命名空间的文本区域。

这里需要搞清楚什么是直接访问:

Python 中不加 . 的访问为直接访问,反之为属性访问。
因此作用域必定是相对某个对象内部而言的,比如一个函数内部、一个模块全局等,那作用域和命名空间是什么关系呢:

  1. 作用域是一种特殊的命名空间,该空间内的名称可以被直接访问;
  2. 并不是所有的命名空间都是作用域。

看不懂? 没关系,后面会解释,我们先回到命名空间这个话题上:

现今 Python 的大部分命名空间是通过字典来实现的,也即一个命名空间就是名字到对象的映射。另外, Python 允许对命名空间进行嵌套,而我们经常接触的命名空间有四层:

LEGB 规则

LEGB 层级

这四层命名空间可以简记为 LEGB:

  1. 局部命名空间(local):指的是一个函数所定义的空间或者一个类的所有属性所在的空间,但需注意的是函数的局部命名空间是一个作用域,而类的局部命名空间不是作用域。
  2. 闭包命名空间(enclosing function):闭包函数 的作用域(Python 3 引入)。
  3. 全局命名空间(global):读入一个模块(也即一个.py文档)后产生的作用域。
  4. 内建命名空间(builtin):Python 解释器启动时自动载入__built__模块后所形成的名字空间;诸如 str/list/dict…等内置对象的名称就处于这里。

为了说清楚这几层洋葱皮,举个栗子: