python:__new__和__init__
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:<class '__main__.Clazz'>, 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:<class '__main__.Clazz'>, 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__方法来创建和初始化一个对象。通过重写这两个方法,我们可以自定义对象的创建和初始化过程,从而实现更加灵活和强大的功能。