06 __init__ 和 __new__的关系和不同
一、 双下new 和 双下init 关系
首先从__new__(cls,a,b,c)的参数说说起,__new__方法的第一个参数是这个类,而其余的参数会在调用成功后全部传递给__init__方法初始化,
所以,__new__方法先于__init__方法执行:
class A: pass class B(A): def __new__(cls): print("__new__方法被执行") return super().__new__(cls) def __init__(self): print("__init__方法被执行") b = B() #先执行new再执行init __new__方法被执行 __init__方法被执行
我们比较两个方法的参数,
可以发现__new__方法是传入类(cls),而__init__方法传入类产生的对象(self),
当你要创建类的对象时,会首先执行元类中的__new__方法,它返回一个空的实例化对象,然后会将该对象自动传给__init__来对这个类进行初始化操作填充属性,
如果new不返回,即返回值为None那么init将不会执行
通常我们定义类的时候只写__init__,来初始化对象,并不是没用__new__,只是内部调用了元类(如:type)的而已,不信你可以手动创建一个new覆盖父类的new,pass掉不返回任何,执行代码会报错,new不工作init就根本不会执行,根本不能实例化出对象
注意:,如果你覆盖了父类的new方法,那么必须保证你的new方法必须有返回值,且必须是对应的类对象,否则init不能工作
二、new的强大之处
我们可以这么理解它们之间的关系,__new__是开辟疆域的大将军,而__init__是在这片疆域上辛勤劳作的小老百姓,只有__new__执行完后,开辟好疆域后,__init__才能工作。
绝大多数情况下,我们都不需要自己重写__new__方法,但在当继承一个不可变的类型(例如str类,int类等)时,它的特性就尤显重要了
可以在我的自定义元类里面定义一个__new__方法,看看它到底是个啥
class Mymeta(type): def __new__(cls, *args, **kwargs): print(cls) print(args) print(kwargs) return type.__new__(cls,*args,**kwargs) class OldboyTeacher(object,metaclass=Mymeta): school = 'oldboy' def __init__(self, name): self.name = name def run(self): print('%s is running' % self.name) """ <class '__main__.Mymeta'> ('OldboyTeacher', (object,), {'__module__': '__main__', '__qualname__': 'OldboyTeacher', 'school': 'oldboy', '__init__': <function OldboyTeacher.__init__ at 0x000000323CEB9510>, 'run': <function OldboyTeacher.run at 0x000000323CEE7158>}) {} """
我们发现__new__里面的*args参数接收到了三个位置参数,并且很容易辨认它们对应的就是类名,类的父类,类体代码执行后的名称空间
那么我们可不可以将__new__()的形参换一种写法:
例子1:
class Mymeta(type): def __new__(cls, class_name,class_bases,class_dic): class_dic['xxx'] = '123' if 'school' in class_dic: class_dic['school'] = 'DSB' return type.__new__(cls,class_name,class_bases,class_dic) class OldboyTeacher(metaclass=Mymeta): school = 'oldboy' def __init__(self,name): self.name = name def run(self): print('%s is running'%self.name) print(OldboyTeacher.xxx) # 发现可以打印出来 123 print(OldboyTeacher.school) # DSB ''' 注意; 如果把__new__换成__init__ 打印xxx属性会报错,找不到 打印school属性,结果是oldboy而不是DSB new可以重新开辟内存空间,新增属性、修改属性,init不行 '''
我们知道字符串是不可改变的,所以使用__init__,传入的字符串相当于已经被打下的疆域,而这块疆域除了将军其他谁也无法改变,__init__只能在这块领地上干瞪眼,此时这块疆域就是”oldboy“。
而使用__new__,相当于__new__大将军自己干活,重新去开辟了一块疆域,所以疆域上的内容可以发生变化,此时这块疆域变成了'123','DSB'。
例子2:
class CapStr(str): def __init__(self, string): string = string.upper() a = CapStr("I love China!") print(a) """ I love China! """
class CapStr(str): def __new__(cls, string): string = string.upper() return super().__new__(cls, string) a = CapStr("I love China!") print(a) """ I LOVE CHINA! """
传入的字符串相当于已经被打下的疆域,而这块疆域除了将军其他谁也无法改变,__init__只能在这块领地上干瞪眼,此时这块疆域就是”I love China!“。而第二个例子中,__new__大将军重新去开辟了一块疆域,所以疆域上的内容也发生了变化,此时这块疆域变成了”I LOVE CHINA!“
结论:
我们可以通过自定义元类,并重写__new__方法来拦截类的创建过程,在类被创建出来之前进行一系列其他操作,实现类一创建出来就自带buff
new方法和init 都可以实现控制类的创建过程,init更简单,但是new功能强大