python--基础知识点--__new__与__init__的区别

真假构造函数

如果你去面试Python工程师的岗位,面试官问你,请问Python当中的类的构造函数是什么?

你不假思索,当然是__init__啦!如果你这么回答,很有可能你就和offer无缘了。因为在Python当中__init__并不是构造函数,__new__才是。是不是有点蒙,多西得(日语:为什么)?我们不是一直将__init__方法当做构造函数来用的吗?怎么又冒出来一个__new__,如果__new__才是构造函数,那么为什么我们创建类的时候从来不用它呢?

别着急,我们慢慢来看。首先我们回顾一下__init__的用法,我们随便写一段代码:

class Student:
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender

我们一直都是这么用的,对不对,毫无问题。但是我们换一个问题,我们在Python当中怎么实现单例(Singleton)的设计模式呢?怎么样实现工厂呢?

从这个问题出发,你会发现只使用__init__函数是不可能完成的,因为__init__并不是构造函数,它只是初始化方法。也就是说在调用__init__之前,我们的实例就已经被创建好了,__init__只是为这个实例赋上了一些值。如果我们把创建实例的过程比喻成做一个蛋糕,__init__方法并不是烘焙蛋糕的,只是点缀蛋糕的。那么显然,在点缀之前必须先烘焙出一个蛋糕来才行,那么这个烘焙蛋糕的函数就是__new__。

__new__函数

我们来看下__new__这个函数的定义,我们在使用Python面向对象的时候,一般都不会重构这个函数,而是使用Python提供的默认构造函数,Python默认构造函数的逻辑大概是这样的:

def __new__(cls, *args, **kwargs):
    return super().__new__(cls, *args, **kwargs)

从代码可以看得出来,函数当中基本上什么也没做,就原封不动地调用了父类的构造函数。这里隐藏着Python当中类的创建逻辑,是根据继承关系一级一级创建的。根据逻辑关系,我们可以知道,当我们创建一个实例的时候,实际上是先调用的__new__函数创建实例,然后再调用__init__对实例进行的初始化。我们可以简单做个实验:

class Test:
    def __new__(cls):
        print('__new__')
        return object().__new__(cls)
    def __init__(self):
        print('__init__')

当我们创建Test这个类的时候,通过输出的顺序就可以知道Python内部的调用顺序。

从结果上来看,和我们的推测完全一样。

单例模式

那么我们重写__new__函数可以做什么呢?一般都是用来完成__init__无法完成的事情,比如前面说的单例模式,通过__new__函数就可以实现。我们来简单实现一下:

在这里插入图片描述
在这里插入图片描述
当然,如果是在并发场景当中使用,还需要加上线程锁防止并发问题,但逻辑是一样的。

除了可以实现一些功能之外,还可以控制实例的创建。因为Python当中是先调用的__new__再调用的__init__,所以如果当调用__new__的时候返回了None,那么最后得到的结果也是None。通过这个特性,我们可以控制类的创建。比如设置条件,只有在满足条件的时候才能正确创建实例,否则会返回一个None。

比如我们想要创建一个类,它是一个int,但是不能为0值,我们就可以利用__new__的这个特性来实现:

class NonZero(int):
    def __new__(cls, value):
        return super().__new__(cls, value) if value != 0 else None

那么当我们用0值来创建它的时候就会得到一个None,而不是一个实例。

总结

共同点:

  • (1) __init__和__new__都是python类中的内置方法
  • (2) __init__和__new__都会在创建对象时自动被调用

不同点:

  • (1) 作用

      __new__创建实例

      __init__初始化实例
  • (2) 运行时间

      __new__方法在__init__方法之前被调用
  • (3) 属于类属性还是实例属性(python中属性和方法都称作属性)

      __new__是类方法,也就是类属性

      __init__是实例方法,也就是实例属性
  • (4) 参数

      __new__参数cls–当前类,调用时需手动绑定(也就是手动传参)将其绑定到cls类上。如:super(A, cls).__new__(cls),其中super(A,cls)可以写为super()。

      __init__参数self–实例化的对象,调用时解释器会自动绑定(也就是解释器会自动将调用对象obj传递给__init__的第一个参数self)将其绑定到self实例对象上。如: super(A, self).__init__(),其中super(A,self)可以写为super()。

    绑定:可以在所调用的方法中使用所绑定对象的某些属性

    辨析super(A,cls)和super(A, self)
  • (5) 可使用属性

      __new__可通过cls使用类属性

      __init__可通过self使用实例属性和类属性
  • (6) 返回值

       __new__必须有返回值。返回值为类本身实例时,会将返回的类本身实例传递给__init__的第一个参数self,并运行__init__对实例进行初始化;返回值为其它类实例时,不会运行__init__方法。

       __init__没有返回值

目前没有搞清的问题:
super().new(cls)和super().init()中的cls、self
(1)__init__为什么需要绑定self
当前解释:初始化时要对具体的实例对象进行初始化,对于其它普通实例方法是要对具体的实例的属性进行操作,因此需要绑定。
(2)__init__为什么是自动绑定
当前解释:当进行初始化时,内存中已经有了实例对象只是还没初始化。普通方法运行时,内存中已经有了初始化后的实例对象。
(3)__new__为什么需要绑定cls
当前解释:猜测创建实例对象的过程中可能需要用到cls所代表的类中的一些信息。
(4)__new__为什么是手动绑定
不清楚。

[参考博客]
https://www.cnblogs.com/techflow/p/13091522.html
https://blog.csdn.net/u011331731/article/details/106440761/
[参考视频]
https://www.bilibili.com/video/BV1g441197MG?p=115

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值