懒得扫全文的童鞋,可以直接跳到最后看总结。
我们先从一个简单的栗子说起:
a 文件中有变量 va
以及类 A
,b 文件导入 a
中class A
,并打印出 A
:
123456789101112131415161718192021 | #a.pyva = [\’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.pyfrom a import A print(\’b\’, A) |
执行 b 文件的结果为:
1234 | Reloaded modules: aa1 2407907960200a2 [\’dobi\’, \’a\’, \’dog\’]b |
可以发现,虽然 b 只是导入了 a 中的 class A
,但导入这个过程却执行了整个 a 文件,那么我们是否能够在 b 中访问 a 中的全局变量 va
呢:
123456 | print(va)# NameError: name \’va\’ is not definedprint(a.va)# NameError: name \’a\’ is not definedprint(b.va)# NameError: name \’b\’ is not defined |
尝试了各类调用方法,发现都无法正常访问 a 的全局变量 va
,既然 b 的导入执行了整个 a 文件,甚至还打印出了 va
的 id
和值,又为什么无法在 b 中调用 va
呢?
这个问题所涉及到的内容就是:命名空间。
但在开始正题之前,我们需要阐明若干概念:
Python 一切皆对象,每个对象都具有 一个ID、一个类型、一个值;对象一旦建立,ID 便不会改变,可以直观的认为 ID 就是对象在内存中的地址:
123456789 | a = [1, 2]b = aid(a)# 2407907978632id(b)# 2407907978632b[1] = 3a# [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)是可以直接访问到命名空间的文本区域。
这里需要搞清楚什么是直接访问:
1234 | #x.pya = 1class A(): def func():pass |
1234 | python x.pya #直接访问# 1A.func #属性访问 |
Python 中不加 .
的访问为直接访问,反之为属性访问。
因此作用域必定是相对某个对象内部而言的,比如一个函数内部、一个模块全局等,那作用域和命名空间是什么关系呢:
看不懂? 没关系,后面会解释,我们先回到命名空间这个话题上:
现今 Python 的大部分命名空间是通过字典来实现的,也即一个命名空间就是名字到对象的映射。另外, Python 允许对命名空间进行嵌套,而我们经常接触的命名空间有四层:
这四层命名空间可以简记为 LEGB:
为了说清楚这几层洋葱皮,举个栗子:
上一篇:理解 WSGI 框架
下一篇:如何成为Python高手