脚本语言诸如python,lua的面向对象与静态语言诸如C++的面向对象本质上有很大的区别。
拿python和C++举例,C++中定义了一个类class,例如:
class Obj
public:
int data;
void mem_func(){};
这里只是声明了一个类的,定义了类的属性和函数。在创建类的对象之前,也就是调用o = Obj()实例化一个对象之前,进程的内存中没有一个显式的变量来保存类的信息的。类的信息转化成代码段或数据段(有静态变量的情况下)写入了可执行文件中,然后被进程加载映射。等到实例化对象的时候才开始为对象实例分配应有的内存。
python定义一个类,例如:
class Obj:
data = 'data'
def cls_fun():
pass
def mem_fun(self):
pass
实际上这段代码被加载时,就在内存里产生了一个对象,这个对象包含了Obj类的信息。也就是内存里有实实在在的对象指向Obj的,这一点与C++是完全不一样的。这里可以看作是在lua中定义了一个table,talbe中有属性字段和函数字段。
那么python类的实例与python类又是什么关系,产生的过程是怎么样的呢?
在我看来他们不是包含关系,也不是子类的关系,用lua的元表可以很好的解释。如果您不懂lua的元表,我可以再解释一遍。
我用粗糙一点的词语来概括,他们是索引关系。这个怎么讲?例如用o = Obj()生成了一个o对象实例,o和Obj在内存中是两个不同的对象,但是o可以索引到Obj对象(类对象,不是实例对象)。为了讲清楚这个概念,我引出另一个概念:作用域。每个对象有各自的作用域,Obj和o的作用域是不同的。Obj的作用域里面有data,cls_fun,mem_fun等字段,o刚开始的时候什么也没有。但是o作为Obj的实例,o是可以访问到Obj的作用域的,这也是类实例的特权之一。我们都知道,类实例既可以访问类实例变量,也可以访问类变量。例如o.data,其实是o先在他自己的作用域中查找,然后再在Obj的作用域查找。明白了这一点,你也能理解为什么类实例变量会隐藏类同名变量了。我想python内部也是这么实现,反正在我写lua语言解释器的时候是这么设计的。
按照这个思路,Obj的方法也只是在Obj的作用域中定义,而没有在o的作用域中定义。o能够调用到Obj的方法,例如o.mem_fun(),是因为先在o的作用域中查找,找不到mem_fun这个字段,然后才在Obj中查找的。方法调用的模式,也是要先转化的,o.mem_fun()其实是转化为了mem_fun(o)。这就是为什么类实例不能调用类方法的原因,例如o.cls_fun()要转化为cls_fun(o),但是cls_fun()是不需要参数,会报错的。
这样看来,其实也没有类方法和类实例方法之分,本质上都是类作用域的一个函数而已。调用类方法时,之所以要用Obj.cls_fun(),是因为要限定Obj这个作用域,因为其他类也可以有这个类方法名。然后类实例调用实例方法时,可以看作是这样的:Obj.mem_fun(o),Obj是他的作用域,o是函数的参数。搞清楚本质之后,理解类方法和类实例方法就非常简单了。记住,类和实例本身不拥有方法,只是方法在类的作用域而已,这点倒与C++的类静态函数和类成员函数的含义一致。
希望通过这篇,您能理解python中类与类实例的关系,这点是非常重要的,后面的继承,多重继承,多态,元类都是基于这个关系的。