1 前言
在python中,每个对象都有两个特殊的方法:__new__和__init__。这两个方法在对象的创建和初始化过程中起着重要的作用,但它们的功能和用法有所不同。
1.1 功能上的区别
__new__方法是python中的一个魔术方法(magic method),用于创建一个新的对象实例。当我们在python中创建一个对象时,实际上是调用了__new__方法来创建一个新的对象实例,然后再调用__init__方法来初始化这个对象。
__init__方法是python中的一个普通方法,用于初始化一个已经存在的对象。当我们使用__new__方法创建一个新的对象实例后(不可为none),就会调用这个对象的__init__方法来对对象进行初始化。
1.2 参数上的区别
__new__方法通常需要三个参数:第一个参数是类(cls,即class),第二个参数是传入的参数列表(args),即位置参数;第三个参数也是传入的参数列表(kwargs),即关键字参数。__new__方法的返回值是一个新的对象实例。
__init__方法通常需要1个或以上参数:第一个参数是对象实例(self,也就是__new__方法返回的对象实例),后续可有可无的若干参数是传入的参数列表(args),常用于设置实例化对象属性。__init__方法的返回值是none。
1.3 调用时机上的区别
__new__方法在创建对象时被调用,它的调用时机是在__init__方法之前。__new__方法的返回值是一个新的对象实例,这个实例会被传递给__init__方法进行初始化。
__init__方法在对象被创建后被调用,它的调用时机是在__new__方法之后。__init__方法用于对已经存在的对象进行初始化,它的参数列表通常包括传递给类的构造函数的参数。
上述可知,没有__new__方法,我们就无法创建新的对象实例;没有__init__方法,我们就无法对已经存在的对象进行初始化。两者功能上虽有差别,但是是用于共同来创建和初始化一个对象的,所以两者均很重要,下面则是具体使用的分析。
2 使用
2.1 简单示例
class clazz: def __new__(cls, *args, **kwargs): print("调用__new__") print(f'cls:{cls}, args:{args}, kwargs: {kwargs}') def __init__(self, name): print("调用__init__") print(f'self:{self}, name:{name}') self.name = name clazz = clazz("xiaoxu")
执行结果:
调用__new__
cls:, args:('xiaoxu',), kwargs: {}
可以看到,上述代码先执行了__new__,但是并未执行__init__方法,因为只有当我们使用__new__方法创建一个新的对象实例后,才会调用这个对象的__init__方法来对对象进行初始化。
__new__是一个内置staticmethod,其首个参数必须是type类型,即要实例化的class本身,其负责为传入的class type分配内存、创建一个新实例并返回该实例,该返回值其实就是后续执行__init__函数的入参self。
参考python的源码typeobject.c中定义的type_call函数:
static pyobject * type_call(pytypeobject *type, pyobject *args, pyobject *kwds) { pyobject *obj; if (type->tp_new == null) { pyerr_format(pyexc_typeerror, "cannot create '%.100s' instances", type->tp_name); return null; } ... obj = type->tp_new(type, args, kwds); # 这里先执行tp_new分配内存、创建对象返回obj obj = _py_checkfunctionresult((pyobject*)type, obj, null); ... type = py_type(obj); # 这里获取obj的class类型,并判定有tp_init则执行该初始化函数 if (type->tp_init != null) { int res = type->tp_init(obj, args, kwds); if (res < 0) { assert(pyerr_occurred()); py_decref(obj); obj = null; } else { assert(!pyerr_occurred()); } } return obj; }
执行代码class(*args, **kwargs) 时,其会先调用type_new函数(__new__方法)分配内存创建实例并返回为obj,而后通过py_type(obj)获取其具体type,再进一步检查type->tp_init不为空则执行该初始化函数(也就是__init__方法)。
若__new__方法返回为none,依然不会执行__init__方法:
class clazz: def __new__(cls, *args, **kwargs): print("调用__new__") print(f'cls:{cls}, args:{args}, kwargs: {kwargs}') return none def __init__(self, name): print("调用__init__") print(f'self:{self}, name:{name}') self.name = name clazz = clazz("xiaoxu") print(clazz) # 调用__new__ # cls:, args:('xiaoxu',), kwargs: {} # none
作如下修改:
class clazz: def __new__(cls, *args, **kwargs): print("调用__new__") print(f'cls:{cls}, args:{args}, kwargs: {kwargs}') # x = super().__new__(cls) 等同写法 x = super(clazz, cls).__new__(cls) print("self_first:", x) return x def __init__(self, name, age=99): print("调用__init__") print(f'self:{self}, name:{name}, age: {age}') super(clazz, self).__init__() self.name = name # cannot return a value from __init__ # __init__是不需要返回值的 # return none clazz = clazz("xiaoxu", age=66) print(clazz)
因为python中任何类都继承于object 类,上述的super().__new__(cls),其实就是调用内置的object.__new__()方法来创建对象实例。一般的形式有super(类名, cls).__new__(cls, … …)。
执行结果如下:
结果也印证了上述提到的,__new__方法的返回值,就是后续执行__init__函数的入参self。
小结说明:
1、继承自object的新式类才有__new__。
2、__new__至少要有一个参数cls,代表当前类,此参数在实例化时由python解释器自动识别。
3、__new__必须要有返回值,返回实例化出来的实例,这点在自己实现__new__时要特别注意,可以return父类(通过super(当前类名, cls))__new__出来的实例,或者直接是object的__new__出来的实例。
4、__init__有一个参数self,就是这个__new__返回的实例,__init__在__new__的基础上可以完成一些其它初始化的动作,__init__不需要返回值。
5、如果__new__创建的是当前类的实例,会自动调用__init__函数,通过return语句里面调用的__new__函数的第一个参数是 cls 来保证是当前类实例,如果是其他类的类名,那么实际创建返回的就是其他类的实例,就不会调用当前类的__init__函数,也不会调用其他类的__init__函数。
6、在定义子类时没有重新定义__new__()时,python默认是调用该类的直接父类的__new__()方法来构造该类的实例,如果该类的父类也没有重写__new__(),那么将一直按此规矩追溯至object的__new__()方法,因为object是所有新式类的基类。
7、如果子类中重写了__new__()方法,那么你可以自由选择任意一个的其他的新式类(必定要是新式类,只有新式类必定都有__new__(),因为所有新式类都是object的后代,而经典类则没有__new__()方法)的__new__()方法来制造实例,包括这个新式类的所有前代类和后代类,只要它们不会造成递归死循环。不能调用自己的__new__,因为是递归死循环调用。
8、对于子类的__init__,其调用规则跟__new__是一致的,当然如果子类和父类的__init__函数都想调用,可以在子类的__init__函数中加入对父类__init__函数的调用。
2.2 __new__的作用
参考python官方文档,__new__方法主要是当你继承一些不可变的class时(比如int, str, tuple), 提供给你一个自定义这些类的实例化过程的途径,另外就是实现自定义的metaclass。
(1)根据int举个栗子:
class positiveinteger(int): def __new__(cls, *args, **kwargs): print("param:", args) return super(positiveinteger, cls).__new__(cls, abs(args[0])) p = positiveinteger(-5) print(p)
执行结果:
(2)再根据tuple举个栗子:
__new__方法自定义要求保证实例创建、并且必须记得返回实例对象的一系列固定逻辑正确,而__init__方法相当简单只需要设置想要设置的属性即可,出错的可能性很小,绝大部分场景用户完全只需要更改__init__方法,用户无需感知__new__的相关逻辑。
理论上是可以通过多次调用__init__函数进行初始化的,但是任何实例都只可能被创建一次,因为每次调用__new__函数理论上都是创建一个新实例返回(特殊情况如单例模式则只返回首次创建的实例),而不会存在重新构造已有实例的情况。
针对__init__可被多次调用的情况,mutable和immutable对象会有不同的行为,因为immutable对象(不可变对象)从语义上来说首次创建、初始化完成后就不可以修改了,所以后续再调用其__init__方法应该无任何效果才对,示例如下:
a = [1, 2, 3] print(id(a), a) # 对list实例重新初始化改变其取值为[4, 5] a.__init__([4, 5]) print(id(a), a) b = (1, 2, 3) print(id(b), b) # 对tuple实例尝试重新初始化并无任何效果, # 符合对immutable类型的行为预期 b.__init__((4, 5)) print(id(b), b)
执行结果如下:
定义、继承immutable class,tuple的栗子:
class positivetuple(tuple): def __init__(self, *args, **kwargs): print('get in init one, self:', id(self), self) # 直接通过索引赋值的方式会报: # positivetuple' object does not support item assignment # for i, x in enumerate(self): # self[i] = abs(x) # 只能尝试对self整体赋值 self = tuple(abs(x) for x in self) print('get in init two, self:', id(self), self) t = positivetuple([-3, -2, 5]) print(id(t), t)
执行结果:
可以看到虽然在__init__中重新对self进行了赋值,其实只是相当于新生成了一个tuple对象28859528,t指向的依然是最开始生成好的实例28847512。
如下为使用自定义__new__的方法:
class positivetuple(tuple): def __new__(cls, *args, **kwargs): self = super().__new__(cls, *args, **kwargs) print('get in init one, self:', id(self), self) # 直接通过索引赋值的方式会报: positivetuple' object does not support item assignment # for i, x in enumerate(self): # self[i] = abs(x) # 只能尝试对self整体赋值 self = tuple(abs(x) for x in self) print('get in init two, self:', id(self), self) return self t = positivetuple([-3, -2, 5]) print(id(t), t)
执行结果如下:
可以看到一开始调用super.__new__时其实已经创建了一个实例27667864,而后通过新生成一个全部转化为正数的tuple 27679880赋值后返回,最终返回的实例t也就最终需要的全正数tuple。
(3)通过__new__方法实现单实例:
class singleton(object): def __new__(cls): if not hasattr(cls, 'instance'): cls.instance = super(singleton, cls).__new__(cls) # 每次生成的都是同一个实例 return cls.instance s1 = singleton() s2 = singleton() s1.attr1 = 'xiaoxu' print(s1.attr1, s2.attr1) print(s1 is s2) # 返回true表明是同一个实例 print(s1 == s2) # xiaoxu xiaoxu # true # true
一般比如字典使用==比较是比较值相等,is是比较地址相等。而在class对象比较中,使用==和is都是比较地址相等(可以通过自定义__eq__来实现想要的效果)。
通过上述的分析,在实际应用中,我们通常就可以同时使用__new__和__init__方法来创建和初始化一个对象。通过重写这两个方法,我们可以自定义对象的创建和初始化过程,从而实现更加灵活和强大的功能。