欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

Python 类的 __new__ 和 __init__ 实现。

最编程 2024-05-26 17:28:31
...

__ new__ 是Python类中最容易滥用的功能之一。 它晦涩难懂,到处都是陷阱,当您确实需要 __ new __ 时,它的功能就非常强大且难以理解。__ new __ 的主要用例在元类中。 元类非常复杂,相信深究Python元类编程的小伙,有本书值得你去读《Python Cookbook 第三版》但这本书要求读者最起码要有Python中级程度的基础,因为作者只给出大量用例,而甚少介绍Python解析器的执行原理,而元类编程的基础大量使用了魔术方法,其中充满陷进,比较难懂的就数__ new __。

class Fruint(object):
    def __init__(self,name):
          self.name=name

//构造
b=Fruit("banana")

在Python以及许多其他语言中,对象分为两个步骤:

  1. 生成对象要访问对象,必须先创建该对象。对象创建就需要为其分配对应类型尺寸的内存空间,该对象存在的唯一标识符就是其内存实体的内存地址,由于Python中的所有对象都继承基类object,因此创建对象会调用object.__ new__,而且__ new __会返回Fruit类的一个实例对象(通常指定为self)
self=obj.__new__(Fruit)

2 .构造对象:生成对象后,Python将该对象实例(,通常用self表示,对于C底层就是该Py结构体的指针)传递给该类的构造函数 __ init __,这一步通常是对类属性初始化调用类内部的类方法,例如如下代码所示

Fruit.__init__(j,"banana")

注意,如果Fruit类重写了基类object的__ init __,情况会稍微复杂一些,例如Fruit类重写了这样函数签名

Fruit. __ new __(cls,name)

此时,不仅有Fruit. __ new __(cls,name)返回对象实例self,而且还有会将参数name传递给Fruit类的 __ init __构造函数,也就是说,就要求程序员必须显式定义Fruit. __ init __(self,name)这样的自定义构造函数和Fruit. __ new __(cls,name)相对应。

重写__ new__方法

因此我们知道 object .__ new__是对象实例化的默认步骤。 这就是从类创建实例的原因。 这是作为Fruit("banana")的第一部分隐式发生的。

注意,直到self通过__ init__运行之后才设置name属性。 因为object .__ new__不调用 __ init__。 它们是不同的方法。 如果我们想对对象实例self在构造函数运行之前对其进行操作,就是在类定义中显式重写__ new__。

Python允许我们通过__ new__ magic方法覆盖任何对象的__ new__。

class Fruit(object):
     def __new__(cls,name):
          obj=super(Fruit,cls).__new__(cls)
          obj.name=name

__ new__将而不是实例作为第一个参数。 由于它创建了一个实例,而使用 super(Fruit,cls). __ new__ (cls)是非常重要,而不能直接调用object .__ new__; 再次,原因后述。

下面的示例,展示了可以在显式重写的__ new__方法中,对定义类属性进行初始化,Python完全允许你这么做,但我们通常只在Python类的元编程才做这样的操作,类属性放在__ init__ 方法下更有意义

class Fruit(object):
    def __new__(cls,name):
        self=super(Fruit,cls).__new__(cls)
        self.name=name
        return self
    
    def __repr__(self):
        return self.name

我们对上面的Fruit类,按照常规的Python类实例化操作

b=Fruit("banana")

或者,我们显式执行Fruit.__ new__(Fruit,"banana"),输出的结果是一样的,但你会认为Python解释器在执行时,会一样吗?读者请自行思考一下。

上买的示例中,即便我们没有在Fruit类中显式定义__ init __方法,但当Python解析器在将Python源代码序列化为字节码的过程中,会隐含地为Fruit类添加默认的构造函数Fruit. __ init __(self,*args,**kwargs),即便这个默认构造是不做任何事

Python类的自定义构造函数

OK,我们通过下面几个反例子,没错,老子最喜欢用反面例子来说明问题的,我们Fruit类显式定义了__ init__构造函数,而且是一个默认构造,尝试运行的话出错,提示的传入参数的个数和显式定义了__ init__构造函数的个数冲突

ss8.png

到这里应该很好理解,我们上文已经说过了,我们只要在Fruit类显式定义Fruit类的自定义构造__ init __(self,name)或 __ init __(self,*args,**kwargs)就可以解决问题

或这样

小结

  • 重要的事情说多一遍无妨,使用Python类的常规的实例化语句,例如:b=Fruit("banana"),Python实际上就分两步执行,详述见上文。

  • 而显式执行Fruit. __ new __ (Fruit,"banana"),只会执行__ new __,而不会执行 __ init __

  • 自定义的__ new __方法必须返回对象实例,而且对象通常有父类调用语句super(Fruit,cls). __ new __ (cls),请好好理解为什么要用关键字super,因为关键字super就是告知当前的子类Fruit“我的父类是object,别无他人”,再来个更复杂一点的

例如类Apple继承自类Fruit,当然类Fruit就继承自object,你会认为Apple.实例化时会跳过Fruit,直接调用object.__ new__(Apple)吗?一旦你这样做会令Apple-->Fruit-->object的任何继承信息(类属性和类方法)缺失(生动地说就是有歪轮常),因此super关键字是告知当前子类它指向子类“相对”的父类。

Ok,Python类的__ new__ ,__ init __和super关键字构成了Python类继承的基石,这些原理性的知识点你理解了,那么写这篇的目地是为Cython类继承做铺垫,这个目地就达成了。

推荐阅读