在 Python 中有一个名词叫做 namespace 单词,这是一个非常重要的概念。
命名空间是一个用来管理所有识别符的抽象机制,这些识别符都要定义在一个特殊范围里,把每一个名字与其相关的值映射在一起。
在 Python 里,函数、类、模块名都是一等类对象,在命名空间里的一个识别符的相关值也许就是一个函数、一个类、或一个模块。
命名空间涉及范围管理,就是常说的 LEGB规则:
L:Local 本地范围,例如全局范围里定义的一个函数体范围。
E:Enclosing 封装范围,例如函数体里定义的一个内部函数体范围。
G:Global 全局范围,就是模块层范围。
B:Built-in 内置范围,就是 Python 天生自带的各个对象名存储的范围。
那么 Python 的面向对象管理就是根据这个命名空间规则进行管理的,这是一个重点。我们通过如下几方面来认识和理解这个概念:
第一,实例和类的命名空间:
实例命名空间负责管理一个独立对象的具体属性。例如一个类的每个实例都彼此不一样,这是一种维护平衡的管理思想,属性的所属也都只与一个实例相关联。即使一个类建立的多个实例有相同的属性,但每个实例的属性就是该实例所拥有,与其它实例无关。
那么除了实例命名空间外,就是类命名空间了。在一个类定义完的时候这个类命名空间就完成了存储工作。类命名空间是用来管理一个类在所有类的实例之间共享的那些成员对象,也就是说,那些不需要指明专属于任何一个实例的方法。
例如:
class A:
def __init__(self):
def this_is_class_A_namespace(self):
class B(A):
group ='Pythonic'
def __init__(self, name):
self.name = name
def this_is_class_B_namespace(self):
b = B('pydojo')
这里我们总共有3个命名空间:
1.类命名空间中存储了类 A 中的 dunder init 和 this_is_class_A_namespace
2.另一个类命名空间中存储了类 B 中的 goup 以及 dunder init 和 this_is_class_B_namespace
3.实例命名空间存储了类B的一个实例的 name 属性。
那么有人可能会问,类 B 如果建立多个实例的话,那么实例之间的命名空间是如何区分的呢?那就与 Python 的命名解决方案和动态调度机制有关了。
我们在写类和实例有关的命名空间时,会建立许多命名空间的入口。在这些入口中会用来获得面向对象编程中的对象名字。在 Python 中这种入口的句法就是一个句号的连写,例如上面的例子中,b.name 就是访问命名空间入口的 Python 句法。
这里我们要知道的是,Python 解释器寻找名字会遵循一个顺序,那就是:
1.先搜索实例命名空间,如果找到了就使用相关的值。
2.在实例命名空间中找不到名字的话,就搜索类命名空间,找到属于一个类的实例再进行名字搜索,如果找到了使用相关的值。
3.如果在继承机制中的类命名空间里找不到名字的话,会继续沿着继承的等级结构,搜索上一级类命名空间,通常就是父类,然后就是爸爸的爸爸,以此类推。
4.如果依然找不到名字的话,解释器会抛出一个 AttributeError 例外错误。
以上描述就是著名的 MRO 命名空间搜索路径机制。
如果子类中覆写了父类中的同名方法,那么在传统的面向对象理论中,Python 会使用名叫动态调度机制来确定名字的所属,在运行时,一个方法的调用都是根据对象的类型,所以就有了确定所属的依据。
这与其它一些编程语言不同,因为它们会使用静态调度机制,这会导致只能在编译代码时才能确定一个调用的依据是一个变量的类型声明。