本篇需要读者提前了解self和cls,这里简述一下:cls是类的引用,self是实例化后的对象的引用,self和cls都是约定俗成的,可以用其他的代替,但是不建议,因为这样会让人困惑,不知道这个参数是什么意思,所以我们还是遵循约定俗成的规则,使用self和cls
1. __new__
__new__
在python中,object是所有类的父类,即所有类都继承自object。__new__则由object基类提供的内置静态方法,用于创建对象并返回对象,是一个类方法,第一个参数是cls,表示当前类,其余参数是用来直接传递给__init__方法的参数。
一句话总结__new__方法的作用:为对象分配空间并返回对象引用
2. __init__
__init__
__init__是一个实例方法,用于初始化对象,即为对象的成员变量赋值,第一个参数是self,表示当前对象,其余参数是用来接收__new__方法传递的参数
一句话总结__init__方法的作用:为初始化对象并其成员变量赋值
__new__和__init__一般要联合使用,下面我们通过一个例子来看一下其使用方法
1 | class Student(object): |
上面例子中我们复写了__new__方法,并且上述例子就是类的正确的实例化过程。下面我们分析一下类实例构建的底层逻辑,加深理解类实例的构造过程
我们有一个Student类,当我们执行stu = Student()时,会首先调用Student类中的__new__方法,他的第一个参数为cls,表示当前类Student,其余的为初始化参数,如之前所述,__new__方法的作用有两个,为对象分配空间,返回对象引用,首先为当前类分配了一块空间来实例化他,然后将其返回,返回到哪里呢?返回到__init__方法中,__init__方法的作用是初始化对象,为其成员变量赋值,__init__方法的第一个参数是self,表示当前实例化后的对象即__new__方法返回的实例化后的Student对象,其余参数是用来接收__new__方法传递的参数,__init__方法执行完毕后,返回Student对象的引用,赋值给stu,这样就完成了类的实例化过程
带类实例属性的实例化过程
下面展示了一个带类实例属性的实例化过程,和上一个例子的区别就是在__init__函数中加入了name变量
1 | class Student(object): |
上述程序执行过程
stu = Student("harry"):开始实例化对象__new__:进入到__new__方法中,首先为Student类分配一块空间,然后返回Student对象的引用__init__:__new__中返回的引用给到__init__的self参数,并将"harry"传递给__init__的name参数,然后执行__init__方法print(stu):将__init__方法返回的引用赋值给stu,然后打印stu,打印出来的是__init__方法返回的引用,即Student的实例化对象
3. super()方法
3.1 super语法
super(type[, object-or-type])
super是一个类,实例化时即实例化super类
- type:类
- object-or-type:类,一般是self,表示当前类的实例化对象
在python3中,在类中可以直接使用super().xxx代替super(Class, self).xxx。但是在多继承中,super()内的参数还是有用的,后两节会详细介绍
继续上述的例子,super()用于调用父类的方法,这里我们使用super()方法调用父类的__new__方法,super()方法的作用是返回当前类的父类,这里我们的父类是object,所以super()方法返回的是object类,然后调用object类的__new__方法,而__new__方法的作用是为对象分配空间并返回对象引用,所以super().__new__(cls)的作用是为Student类分配空间并返回Student对象的引用
上述例子就是super在单继承中的作用,下面我们分别看一下在单继承和多继承中的作用
3.2 super在单继承中的作用
本例子均来源于菜鸟Python
1 | class A: |
上述例子中,B类继承了A类,B类中的add方法中调用了super().add(m)(如果是python2需要改为super(B, self).add(m))。首先实例化了B类,然后调用B类中的add函数,运行到super().add(m)时,会调用父类中的add函数,而super中的self是B类的实例化对象,所以self.n的值是3而不是类A中的2。n值的最终计算过程为self.n=3加上在A类中的self.n+2=5,然后回到B类中,self.n+3=8,所以最终的结果为8
3.3 super在多继承中的作用
在单继承过程中,我们直接调用super()方法是没有问题的,不需要加上额外的参数,但是涉及到多继承,会涉及到查找顺序(MRO)的问题,MRO就是类方法的解析顺序表,即继承父类方法时的顺序表。我们直接看下面例子,类B和C继承了类A,然后类D继承了B,C,即继承了两个
1 | class A: |
MRO顺序表
这里我们实例化了类D,并调用了add方法,运行到super(D, self).add(m)这一行代码时,所以涉及MRO,在本例中,MRO顺序表为[D, B, C, A, Object]。python也提供了一个类内属性来输出MRO顺序,这里我们运行print(D.__mro__),输出
1 | (<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) |
MRO有以下特性
- 搜索方法时,按照MRO的的结果从左到右顺序搜索
- 找到方法后,就直接执行,不在继续搜索,如果没有找到抛出异常
- 在python3中,
super().xxx表示在继承的所有类中进行查找,也可以指定,例如本例中如果执行super(B, self).add(m)表示在B之后的父类中进行查找,即[C, A, Object]
代码执行过程
由于我们调用的是super(D, self).add(m),因此在实例化时只会在D之后的类中找,即[B, C, A, Object],这里只有[B, C, A]中有add函数,所以会依次调用在BCA中的add方法。如上所示,初始self.n=5
- 先搜索
B,B中有add函数,但是在B中又调用了super函数,所以继续检索 - 检索
C,C中有add函数,但是在C中又调用了super函数,所以继续检索 - 检索
A,A中有add函数,开始运行self.n+2=7 - 回到
C中,self.n+4=11 - 回到
B中,self.n+3=14 - 回到
D中,self.n+5=19