真假构造函数
如果你去面试Python工程师的岗位,面试官问你,请问Python当中的类的构造函数是什么?
你不假思索,当然是__init__啦!如果你这么回答,很有可能你就和offer无缘了。因为在Python当中__init__并不是构造函数,__new__才是。是不是有点蒙,多西得(日语:为什么)?我们不是一直将__init__方法当做构造函数来用的吗?怎么又冒出来一个__new__,如果__new__才是构造函数,那么为什么我们创建类的时候从来不用它呢?
别着急,我们慢慢来看。首先我们回顾一下__init__的用法,我们随便写一段代码:
class Student:
def __init__(self, name, gender):
self.name = name
self.gender = gender
我们一直都是这么用的,对不对,毫无问题。但是我们换一个问题,我们在Python当中怎么实现单例(Singleton)的设计模式呢?怎么样实现工厂呢?
从这个问题出发,你会发现只使用__init__函数是不可能完成的,因为__init__并不是构造函数,它只是初始化方法。也就是说在调用__init__之前,我们的实例就已经被创建好了,__init__只是为这个实例赋上了一些值。如果我们把创建实例的过程比喻成做一个蛋糕,__init__方法并不是烘焙蛋糕的,只是点缀蛋糕的。那么显然,在点缀之前必须先烘焙出一个蛋糕来才行,那么这个烘焙蛋糕的函数就是__new__。
__new__函数
我们来看下__new__这个函数的定义,我们在使用Python面向对象的时候,一般都不会重构这个函数,而是使用Python提供的默认构造函数,Python默认构造函数的逻辑大概是这样的:
def __new__(cls, *args, **kwargs):
return super().__new__(cls, *args, **kwargs)
从代码可以看得出来,函数当中基本上什么也没做,就原封不动地调用了父类的构造函数。这里隐藏着Python当中类的创建逻辑,是根据继承关系一级一级创建的。根据逻辑关系,我们可以知道,当我们创建一个实例的时候,实际上是先调用的__new__函数创建实例,然后再调用__init__对实例进行的初始化。我们可以简单做个实验:
class Test:
def __new__(cls):
print('__new__')
return object().__new__(cls)
def __init__(self):
print('__init__')
当我们创建Test这个类的时候,通过输出的顺序就可以知道Python内部的调用顺序。
从结果上来看,和我们的推测完全一样。
单例模式
那么我们重写__new__函数可以做什么呢?一般都是用来完成__init__无法完成的事情,比如前面说的单例模式,通过__new__函数就可以实现。我们来简单实现一下:
当然,如果是在并发场景当中使用,还需要加上线程锁防止并发问题,但逻辑是一样的。
除了可以实现一些功能之外,还可以控制实例的创建。因为Python当中是先调用的__new__再调用的__init__,所以如果当调用__new__的时候返回了None,那么最后得到的结果也是None。通过这个特性,我们可以控制类的创建。比如设置条件,只有在满足条件的时候才能正确创建实例,否则会返回一个None。
比如我们想要创建一个类,它是一个int,但是不能为0值,我们就可以利用__new__的这个特性来实现:
class NonZero(int):
def __new__(cls, value):
return super().__new__(cls, value) if value != 0 else None
那么当我们用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()。
绑定:可以在所调用的方法中使用所绑定对象的某些属性
辨析super(A,cls)和super(A, self)
- (5) 可使用属性
__new__可通过cls使用类属性
__init__可通过self使用实例属性和类属性
- (6) 返回值
__new__必须有返回值。返回值为类本身实例时,会将返回的类本身实例传递给__init__的第一个参数self,并运行__init__对实例进行初始化;返回值为其它类实例时,不会运行__init__方法。
__init__没有返回值
目前没有搞清的问题:
super().new(cls)和super().init()中的cls、self
(1)__init__为什么需要绑定self
当前解释:初始化时要对具体的实例对象进行初始化,对于其它普通实例方法是要对具体的实例的属性进行操作,因此需要绑定。
(2)__init__为什么是自动绑定
当前解释:当进行初始化时,内存中已经有了实例对象只是还没初始化。普通方法运行时,内存中已经有了初始化后的实例对象。
(3)__new__为什么需要绑定cls
当前解释:猜测创建实例对象的过程中可能需要用到cls所代表的类中的一些信息。
(4)__new__为什么是手动绑定
不清楚。
[参考博客]
https://www.cnblogs.com/techflow/p/13091522.html
https://blog.csdn.net/u011331731/article/details/106440761/
[参考视频]
https://www.bilibili.com/video/BV1g441197MG?p=115