真假构造函数
如果你去面试Python工程师的岗位,面试官问你,请问Python当中的类的构造函数是什么?
你不假思索,当然是__init__啦!如果你这么回答,很有可能你就和offer无缘了。因为在Python当中__init__并不是构造函数,__new__才是。是不是有点蒙,多西得(日语:为什么)?我们不是一直将__init__方法当做构造函数来用的吗?怎么又冒出来一个__new__,如果__new__才是构造函数,那么为什么我们创建类的时候从来不用它呢?
别着急,我们慢慢来看。首先我们回顾一下__init__的用法,我们随便写一段代码:
classStudent:def__init__(self,name,gender):self.name=name self.gender=gender
1
2
3
4
我们一直都是这么用的,对不对,毫无问题。但是我们换一个问题,我们在Python当中怎么实现单例(Singleton)的设计模式呢?怎么样实现工厂呢?
从这个问题出发,你会发现只使用__init__函数是不可能完成的,因为__init__并不是构造函数,它只是初始化方法。也就是说在调用__init__之前,我们的实例就已经被创建好了,__init__只是为这个实例赋上了一些值。如果我们把创建实例的过程比喻成做一个蛋糕,__init__方法并不是烘焙蛋糕的,只是点缀蛋糕的。那么显然,在点缀之前必须先烘焙出一个蛋糕来才行,那么这个烘焙蛋糕的函数就是__new__。
我们来看下__new__这个函数的定义,我们在使用Python面向对象的时候,一般都不会重构这个函数,而是使用Python提供的默认构造函数,Python默认构造函数的逻辑大概是这样的:
def__new__(cls,*args,**kwargs):returnsuper().__new__(cls,*args,**kwargs)
1
2
从代码可以看得出来,函数当中基本上什么也没做,就原封不动地调用了父类的构造函数。这里隐藏着Python当中类的创建逻辑,是根据继承关系一级一级创建的。根据逻辑关系,我们可以知道,当我们创建一个实例的时候,实际上是先调用的__new__函数创建实例,然后再调用__init__对实例进行的初始化。我们可以简单做个实验:
classTest:def__new__(cls):print('__new__')returnobject().__new__(cls)def__init__(self):print('__init__')
1
2
3
4
5
6
当我们创建Test这个类的时候,通过输出的顺序就可以知道Python内部的调用顺序。
从结果上来看,和我们的推测完全一样。
那么我们重写__new__函数可以做什么呢?一般都是用来完成__init__无法完成的事情,比如前面说的单例模式,通过__new__函数就可以实现。我们来简单实现一下:
当然,如果是在并发场景当中使用,还需要加上线程锁防止并发问题,但逻辑是一样的。
除了可以实现一些功能之外,还可以控制实例的创建。因为Python当中是先调用的__new__再调用的__init__,所以如果当调用__new__的时候返回了None,那么最后得到的结果也是None。通过这个特性,我们可以控制类的创建。比如设置条件,只有在满足条件的时候才能正确创建实例,否则会返回一个None。
比如我们想要创建一个类,它是一个int,但是不能为0值,我们就可以利用__new__的这个特性来实现:
classNonZero(int):def__new__(cls,value):returnsuper().__new__(cls,value)ifvalue!=0elseNone
1
2
3
那么当我们用0值来创建它的时候就会得到一个None,而不是一个实例。
共同点:
(1) __init__和__new__都是python类中的内置方法
(2) __init__和__new__都会在创建对象时自动被调用
不同点:
(1) 作用
__new__创建实例
__init__初始化实例
(2) 运行时间
__new__方法在__init__方法之前被调用
(3) 属于类属性还是实例属性(python中属性和方法都称作属性)
__new__是类方法,也就是类属性
__init__是实例方法,也就是实例属性
(4) 参数
__new__参数cls–当前类,调用时需手动绑定(也就是手动传参)将其绑定到cls类上。如:super(A, cls).__new__(cls),其中super(A,cls)可以写为super()。
__init__参数self–实例化的对象,调用时解释器会自动绑定(也就是解释器会自动将调用对象obj传递给__init__的第一个参数self)将其绑定到self实例对象上。如: super(A, self).__init__(),其中super(A,self)可以写为super()。
绑定:可以在所调用的方法中使用所绑定对象的某些属性
(5) 可使用属性
__new__可通过cls使用类属性
__init__可通过self使用实例属性和类属性
(6) 返回值
__new__必须有返回值。返回值为类本身实例时,会将返回的类本身实例传递给__init__的第一个参数self,并运行__init__对实例进行初始化;返回值为其它类实例时,不会运行__init__方法。
__init__没有返回值