懒得扫全文的童鞋,可以直接跳到最后看总结。
我们先从一个简单的栗子说起:
栗子
a 文件中有变量 va
以及类 A
,b 文件导入 a
中class A
,并打印出 A
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#a.py va = ['dobi', 'a', 'dog'] print('a1', id(va)) class A(): def __init__(self): pass def rtn(self): global va va.insert(1,'is') print('a3', id(va)) return va print('a2', va) #b.py from a import A print('b', A) |
执行 b 文件的结果为:
1 2 3 4 |
Reloaded modules: a a1 2407907960200 a2 ['dobi', 'a', 'dog'] b |
可以发现,虽然 b 只是导入了 a 中的 class A
,但导入这个过程却执行了整个 a 文件,那么我们是否能够在 b 中访问 a 中的全局变量 va
呢:
1 2 3 4 5 6 |
print(va) # NameError: name 'va' is not defined print(a.va) # NameError: name 'a' is not defined print(b.va) # NameError: name 'b' is not defined |
尝试了各类调用方法,发现都无法正常访问 a 的全局变量 va
,既然 b 的导入执行了整个 a 文件,甚至还打印出了 va
的 id
和值,又为什么无法在 b 中调用 va
呢?
这个问题所涉及到的内容就是:命名空间。
但在开始正题之前,我们需要阐明若干概念:
一些基本概念的澄清
对象
Python 一切皆对象,每个对象都具有 一个ID、一个类型、一个值;对象一旦建立,ID 便不会改变,可以直观的认为 ID 就是对象在内存中的地址:
1 2 3 4 5 6 7 8 9 |
a = [1, 2] b = a id(a) # 2407907978632 id(b) # 2407907978632 b[1] = 3 a # [1, 3] |
上例 a, b 共享了同一个 ID、同一个值、同一个类型。因此 a, b 表达的是同一个对象,但 a, b 又明显是不同的,比如一个叫 'a'
一个叫 'b'
…既然是同一个对象,为什么又有不同的名字呢?难道名字不是对象的属性?
标识符
事实确实如此,这是 Python 比较特殊一点:如同'a'
'b'
这样的名称其实有一个共同的名字:identifier(注意不要与 ID 混淆了),中文名为“标识符”,来解释一下:
标识符:各类对象的名称,比如函数名、方法名、类名,变量名、常量名等。
在 Python 中赋值并不会直接复制数据,而只是将名称绑定到对象,对象本身是不知道也不需要关心(该关心这个的是程序猿)自己叫什么名字的。一个对象甚至可以指向不同的标识符,上例中的'a'
'b'
便是如此。真正管理这些名子的事物就是本文的主角”命名空间” 。
命名空间
命名空间:(英语:Namespace)表示标识符(identifier)的可见范围。(ps:copy 自 SF)
简而言之,命名空间可以被简单的理解为:存放和使用对象名字的抽象空间。
作用域
与命名空间相对的一个概念就是“作用域”,那么什么又是作用域呢?
作用域:(英文 Scope)是可以直接访问到命名空间的文本区域。
这里需要搞清楚什么是直接访问:
1 2 3 4 |
#x.py a = 1 class A(): def func():pass |
1 2 3 4 |
python x.py a #直接访问 # 1 A.func #属性访问 |
Python 中不加 .
的访问为直接访问,反之为属性访问。
因此作用域必定是相对某个对象内部而言的,比如一个函数内部、一个模块全局等,那作用域和命名空间是什么关系呢:
- 作用域是一种特殊的命名空间,该空间内的名称可以被直接访问;
- 并不是所有的命名空间都是作用域。
看不懂? 没关系,后面会解释,我们先回到命名空间这个话题上:
现今 Python 的大部分命名空间是通过字典来实现的,也即一个命名空间就是名字到对象的映射。另外, Python 允许对命名空间进行嵌套,而我们经常接触的命名空间有四层:
LEGB 规则
LEGB 层级
这四层命名空间可以简记为 LEGB:
- 局部命名空间(local):指的是一个函数所定义的空间或者一个类的所有属性所在的空间,但需注意的是函数的局部命名空间是一个作用域,而类的局部命名空间不是作用域。
- 闭包命名空间(enclosing function):闭包函数 的作用域(Python 3 引入)。
- 全局命名空间(global):读入一个模块(也即一个.py文档)后产生的作用域。
- 内建命名空间(builtin):Python 解释器启动时自动载入__built__模块后所形成的名字空间;诸如 str/list/dict…等内置对象的名称就处于这里。
为了说清楚这几层洋葱皮,举个栗子: