python魔法方法(二): __new__和__init__

本篇需要读者提前了解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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Student(object):

def __new__(cls, *args, **kwargs):
print("this is new 方法")
return super().__new__(cls)

def __init__(self):
print("this is init 方法")


stu = Student()
print(stu)

'''
this is new 方法
this is init 方法
<__main__.Student object at 0x7f9312f9a740>
'''

上面例子中我们复写了__new__方法,并且上述例子就是类的正确的实例化过程。下面我们分析一下类实例构建的底层逻辑,加深理解类实例的构造过程

我们有一个Student类,当我们执行stu = Student()时,会首先调用Student类中的__new__方法,他的第一个参数为cls,表示当前类Student,其余的为初始化参数,如之前所述,__new__方法的作用有两个,为对象分配空间,返回对象引用,首先为当前类分配了一块空间来实例化他,然后将其返回,返回到哪里呢?返回到__init__方法中,__init__方法的作用是初始化对象,为其成员变量赋值__init__方法的第一个参数是self,表示当前实例化后的对象即__new__方法返回的实例化后的Student对象,其余参数是用来接收__new__方法传递的参数,__init__方法执行完毕后,返回Student对象的引用,赋值给stu,这样就完成了类的实例化过程

带类实例属性的实例化过程

下面展示了一个带类实例属性的实例化过程,和上一个例子的区别就是在__init__函数中加入了name变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Student(object):

def __new__(cls, *args, **kwargs):
print("this is new 方法")
return super().__new__(cls)

def __init__(self, name):
self.name = name
print("this is init 方法")
print("Student's name is: ", self.name)


stu = Student("harry")
print(stu)

'''
this is new 方法
this is init 方法
Student's name is: harry
<__main__.Student object at 0x7f529854a770>
'''

上述程序执行过程

  • 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class A:
def __init__(self):
self.n = 2

def add(self, m):
print('self is {0} @A.add'.format(self))
self.n += m


class B(A):
def __init__(self):
self.n = 3

def add(self, m):
print('self is {0} @B.add'.format(self))
super().add(m)
self.n += 3

b = B()
b.add(2)
print(b.n)

'''
self is <__main__.B object at 0x7fdc45a3be50> @B.add
self is <__main__.B object at 0x7fdc45a3be50> @A.add
8
'''

上述例子中,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就是类方法的解析顺序表,即继承父类方法时的顺序表。我们直接看下面例子,类BC继承了类A,然后类D继承了B,C,即继承了两个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class A:
def __init__(self):
self.n = 2

def add(self, m):
print('self is {0} @A.add'.format(self))
self.n += m


class B(A):
def __init__(self):
self.n = 3

def add(self, m):
print('self is {0} @B.add'.format(self))
super().add(m)
self.n += 3

class C(A):
def __init__(self):
self.n = 4

def add(self, m):
print('self is {0} @C.add'.format(self))
super().add(m)
self.n += 4


class D(B, C):
def __init__(self):
self.n = 5

def add(self, m):
print('self is {0} @D.add'.format(self))
super(D, self).add(m)
self.n += 5

d = D()
d.add(2)
print(d.n)

'''
self is <__main__.D object at 0x7f0f1f6bfdc0> @D.add
self is <__main__.D object at 0x7f0f1f6bfdc0> @B.add
self is <__main__.D object at 0x7f0f1f6bfdc0> @C.add
self is <__main__.D object at 0x7f0f1f6bfdc0> @A.add
19
'''

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

  • 先搜索BB中有add函数,但是在B中又调用了super函数,所以继续检索
  • 检索CC中有add函数,但是在C中又调用了super函数,所以继续检索
  • 检索AA中有add函数,开始运行self.n+2=7
  • 回到C中,self.n+4=11
  • 回到B中,self.n+3=14
  • 回到D中,self.n+5=19
Error: API rate limit exceeded for 54.237.145.67. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.)