python:__new__和__init__

news2024/11/24 2:49:08

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1673132.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【管理咨询宝藏101】普华永道并购尽调内部培训

【管理咨询宝藏101】普华永道并购尽调内部培训 【格式】PDF版本 【关键词】普华永道、兼并收购、尽职调查 【核心观点】 - 尽职调查的目的&#xff0c;发现潜在的致命缺陷&#xff0c;判断是否继续交易进程&#xff1b;发现潜在的问题&#xff0c;制定交易前后相应的应对措施。…

换个暴露又发一区(IF=10.1)!双样本孟德尔随机化+脂质组学发了高分

孟德尔随机化的热度一直很高&#xff0c;不少想发文的对此肯定又爱又恨。今天我们看的这篇文章就仅用了双样本孟德尔随机化的方法&#xff0c;看似显而易见的关系&#xff0c;竟然结合了脂质组学&#xff0c;立马升华&#xff0c;发表一区&#xff08;IF10.1&#xff09;&#…

ARIMA预测模型介绍

ARIMA&#xff08;Autoregressive Integrated Moving Average&#xff09;模型是一种常用的时间序列分析方法&#xff0c;能够对非平稳时间序列进行建模和预测。本文将详细介绍ARIMA模型的建模步骤&#xff0c;包括数据预处理、模型识别、参数估计和模型检验等环节&#xff0c;…

`unordered_map` 和 `unordered_set`

unordered —— 无序的&#xff0c;从表面上来看&#xff0c;与 map 和 set 不同之处就在于&#xff0c;unordered_map 和 unordered_set 无法保证插入数据是有序的&#xff1b; 尽管如此&#xff0c;由于这两种容器内部封装了“哈希桶”&#xff0c;可以实现快速查找数据 ——…

白话机器学习5:卷积神经网络(CNN)原理

1.神经元 激活函数f(z)的种类&#xff1a; 2.卷积方法种类 https://mp.weixin.qq.com/s/FXzTbMG64jr93Re31Db2EA 标准卷积&#xff08;Standard Convolution&#xff09;: 特点&#xff1a;每个卷积核在输入数据的整个深度上滑动&#xff0c;计算输出特征图的一个元素。应用场…

STM32有什么高速接口吗?

STM32 有一些高速接口&#xff0c;比如 USART、SPI、I2C 等&#xff0c;这些接口可以用于与外部设备进行高速数据传输。我这里有一套stm32入门教程&#xff0c;不仅包含了详细的视频讲解&#xff0c;项目实战。如果你渴望学习stm32&#xff0c;不妨点个关注&#xff0c;给个评论…

JavaSE——集合框架一(2/7)-Collection集合的遍历方式-迭代器、增强for循环、Lambda、案例

目录 Collection的遍历方式 迭代器 增强for循环&#xff08;foreach&#xff09; Lambda表达式遍历集合 案例 需求与分析 代码部分 运行结果 Collection的遍历方式 迭代器 选代器是用来遍历集合的专用方式&#xff08;数组没有选代器&#xff09;&#xff0c;在Java中…

【爬虫之scrapy框架——尚硅谷(学习笔记one)--基本步骤和原理+爬取当当网(基本步骤)】

爬虫之scrapy框架——基本原理和步骤爬取当当网&#xff08;基本步骤&#xff09; 下载scrapy框架创建项目&#xff08;项目文件夹不能使用数字开头&#xff0c;不能包含汉字&#xff09;创建爬虫文件&#xff08;1&#xff09;第一步&#xff1a;先进入到spiders文件中&#x…

LabVIEW开发RS422通信

LabVIEW开发RS422通信 项目围绕LabVIEW软件开发的程序在RS422通信技术检测方面的应用进行展开&#xff0c;通过软件编程将上位计算机虚拟化为检测设备&#xff0c;控制其通信端口与被测产品进行RS422通信&#xff0c;以此检验产品的性能优劣。该虚拟检测仪器在实际测试中表现出…

C++17新特性 结构化绑定

一、Python中的相似功能 熟悉python的应该对下面的代码很熟悉 def return_multiple_values():return 11, 7x, y return_multiple_values()函数返回一个元组&#xff0c;元组自动分配给了x和y。 二、C11中的元组 c11中就存在类似python元组的概念了&#xff1a; std::tupl…

【简单介绍下Milvus】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

CV每日论文--2024.5.10

1、Attention-Driven Training-Free Efficiency Enhancement of Diffusion Models 中文标题&#xff1a;扩散模型的注意力驱动的训练免费效率增强 简介&#xff1a;扩散模型在生成高质量和多样化图像方面取得了出色的表现,但其卓越性能是以昂贵的架构设计为代价的,特别是广泛使…

激光SLAM总结——Fast LIO / Fast LIO2 / Faster LIO

激光SLAM总结——Fast LIO / Fast LIO2 / Faster LIO 在之前的工作中有接触过LOAM&#xff0c;最近在工作中又接触到Faster LIO相关的工作&#xff0c;于是想着对Fast LIO / Fast LIO2 / Faster LIO这一系列工作进行一个简单的总结&#xff0c;以加深自己对激光SLAM算法的理解…

网络网络层之(5)IPv6协议

网络网络层之(5)IPv6协议 Author: Once Day Date: 2024年5月12日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文档可参考专栏&#xff1a;通信网络技术_Once-Day…

未授权访问:Docker未授权访问漏洞

目录 1、漏洞原理 2、环境搭建 3、未授权访问 4、通过crontab反弹宿主机shell 防御手段 今天继续学习各种未授权访问的知识和相关的实操实验&#xff0c;一共有好多篇&#xff0c;内容主要是参考先知社区的一位大佬的关于未授权访问的好文章&#xff0c;还有其他大佬总结好…

【机器学习】机器学习与人工智能融合新篇章:自适应智能代理在多元化复杂环境中的创新应用与演进趋势

&#x1f512;文章目录&#xff1a; &#x1f4a5;1.引言 &#x1f68b;1.1 机器学习与人工智能的发展背景 &#x1f68c;1.2 自适应智能代理的概念与重要性 &#x1f690;1.3 研究目的与意义 ☔2.自适应智能代理的关键技术 &#x1f6e3;️2.1 环境感知与信息处理技术 …

whisper报错:hp, ht, pid, tid = _winapi.CreateProcess [WinError 2] 系统找不到指定的文件。

in _execute_child hp&#xff0c; ht&#xff0c; pid&#xff0c; tid _winapi.CreateProcess&#xff08;executable&#xff0c; args&#xff0c; FileNotFoundError&#xff1a; [WinError 2] 系统找不到指定的文件。 原因&#xff1a; 没装ffmpeg 或者 ffmpeg没添加到…

Nios实验使用串口输出“Hello Nios-II”字符到笔记本电脑

目录 实验过程 创建工程 修改程序 编译工程 运行项目 效果实现 总结 参考 实验过程 硬件设计见博主上篇博客 软件部分设计 下面使用 Nios II Software Build Tools for Eclipse 来完成当前项目的软件开发。 启动 Nios II SBT 按照下图所示点击 Nios II Software Build…

Backend - 数据分析 Pandas

目录 一、作用 二、基础环境 &#xff08;一&#xff09;执行虚拟环境的终端命令 &#xff08;二&#xff09;代码中导包 三、应用&#xff1a;一维数组 &#xff08;一&#xff09;Series对象 1. 含义 2. 常用属性和方法 &#xff08;1&#xff09;属性 &#xff08;…

springboot学习整理

视频&#xff1a;基础篇-01_springboot概述_哔哩哔哩_bilibili 介绍 spring boot 是spring提供的一个子项目&#xff0c;用于快速构建spring应用程序 spring构建&#xff1a; 1 导入依赖繁琐 &#xff1b; 2 项目配置繁琐 spring Framework: 核心 spring Boot :快速构建spring…