本篇需要读者提前了解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