python:__new__和__init__

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

  • 18
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值