python中__new__和__init__的实现

 更新时间:2024年05月14日 10:05:19   作者:小徐也要努力鸭  
在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__方法来创建和初始化一个对象。通过重写这两个方法,我们可以自定义对象的创建和初始化过程,从而实现更加灵活和强大的功能。

到此这篇关于python中__new__和__init__的实现的文章就介绍到这了,更多相关python __new__和__init__内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Python周期任务神器之Schedule模块使用详解

    Python周期任务神器之Schedule模块使用详解

    这篇文章主要为大家详细介绍了Python中的周期任务神器—Schedule模块的安装和初级、进阶使用方法,文中的示例代码讲解详细,需要的可以参考一下
    2022-04-04
  • 利用Python破解验证码实例详解

    利用Python破解验证码实例详解

    这篇文章主要给大家介绍的是如何通过一个简单的例子来实现破解验证码。从本文中我们可以学习到 Python 基本知识,PIL 模块的使用,破解验证码的原理。文中通过实例一步步介绍的很详细,相信对大家理解和学习具有一定的参考借鉴价值,有需要的朋友们下面来一起看看吧。
    2016-12-12
  • python基本算法之实现归并排序(Merge sort)

    python基本算法之实现归并排序(Merge sort)

    这篇文章主要给大家介绍了关于python基本算法之实现归并排序(Merge sort)的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • 科学Python开发环境Spyder必知必会点

    科学Python开发环境Spyder必知必会点

    这篇文章主要为大家介绍了科学Python开发环境Spyder必知必会点及使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2024-01-01
  • Python数据持久化存储实现方法分析

    Python数据持久化存储实现方法分析

    这篇文章主要介绍了Python数据持久化存储实现方法,结合实例形式分析了Python基于pymongo及mysql模块的数据持久化存储操作相关实现技巧,需要的朋友可以参考下
    2019-12-12
  • python输出带有颜色字体的三种方法

    python输出带有颜色字体的三种方法

    在使用python运维与开发的过程中,经常需要打印显示各种信息,海量的信息堆砌在控制台中,就会导致各种信息都显示在一起,降低了重要信息的可读性,这时候如果能给重要的信息加上差异的字体颜色,那么就会更加显眼,所以本文给大家介绍了python输出带有颜色文字的三种方法
    2024-05-05
  • python人工智能算法之线性回归实例

    python人工智能算法之线性回归实例

    这篇文章主要为大家介绍了python人工智能算法之线性回归实例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • Python中字符串对象语法分享

    Python中字符串对象语法分享

    这篇文章主要介绍了Python中字符串对象语法分享,前面提到了Python中的数值型内置数据类型,接下来呢我们就着重介绍一下字符串类型,需要的朋友可以参考一下
    2022-02-02
  • Django集成富文本编辑器summernote的实现步骤

    Django集成富文本编辑器summernote的实现步骤

    在最近的项目中小编使用了这个富文本编辑器,选择它的主要原因是配置非常简单,默认支持普通用户上传图片(不像ckeditor默认只有staff user才能上传图片。如果要让普通用户上传图片,还需修改源码装饰器)。现在让我们来看看如何使用这个富文本编辑器
    2021-05-05
  • python放大图片和画方格实现算法

    python放大图片和画方格实现算法

    这篇文章主要为大家详细介绍了python放大图片和画方格实现算法,实现图片的放大缩小功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-03-03

最新评论