请选择 进入手机版 | 继续访问电脑版

湖南新梦想

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 78|回复: 0

Python 中对象是如何被调用的?(三)

[复制链接]

3099

主题

3499

帖子

1万

积分

论坛元老

Rank: 8Rank: 8

积分
12384
发表于 2022-6-28 15:42:11 | 显示全部楼层 |阅读模式
我们说tp_new应该返回该类型对象的实例对象,而且一般情况下我们是不写__new__的,会默认执行。但是我们一旦重写了,那么必须要手动返回object.__new__(cls)。可如果我们不返回,或者返回其它的话,会怎么样呢?
  1.   class Girl:
  2.      def __new__(cls, *args, **kwargs):
  3.          print("__new__方法执行啦")
  4.          instance = object.__new__(cls)
  5.          # 打印看看instance到底是个什么东东
  6.          print("instance:", instance)
  7.          print("type(instance):", type(instance))
  8.          # 正确做法是将instance返回
  9.          # 但是我们不返回, 而是返回个 123
  10.          return 123
  11.      def __init__(self, name, age):
  12.          print("__init__方法执行啦")
  13.   g = Girl()
  14.   """
  15.   __new__方法执行啦
  16.   instance: <__main__.Girl object at 0x000002C0F16FA1F0>
  17.   type(instance): <class '__main__.Girl'>
  18.   """
复制代码

  这里面有很多可以说的点,首先就是 __init__ 里面需要两个参数,但是我们没有传,却还不报错。原因就在于这个 __init__ 压根就没有执行,因为 __new__ 返回的不是 Girl 的实例对象。
  通过打印 instance,我们知道了object.__new__(cls) 返回的就是 cls 的实例对象,而这里的cls就是Girl这个类本身。我们必须要返回instance,才会执行对应的__init__,否则__new__直接就返回了。我们在外部来打印一下创建的实例对象吧,看看结果:
  1.   class Girl:
  2.      def __new__(cls, *args, **kwargs):
  3.          return 123
  4.      def __init__(self, name, age):
  5.          print("__init__方法执行啦")
  6.   g = Girl()
  7.   print(g, type(g))  # 123 <class 'int'>
复制代码

  我们看到打印的是123,所以再次总结一些tp_new和tp_init之间的区别,当然也对应__new__和__init__的区别:
  ·tp_new:为该类型对象的实例对象申请内存,在Python的__new__方法中通过object.__new__(cls)的方式申请,然后将其返回
  · tp_init:tp_new的返回值会自动传递给self,然后为self绑定相应的属性,也就是进行实例对象的初始化
  但如果tp_new返回的不是对应类型的实例对象的指针,比如type_call中第一个参数接收的&PyFloat_Type,但是tp_new中返回的却是PyLongObject *,所以此时就不会执行tp_init。
  以上面的代码为例,我们Girl中的__new__应该返回Girl的实例对象才对,但实际上返回了整型,因此类型不一致,所以不会执行__init__。
  下面我们可以做总结了,通过类型对象去创建实例对象的整体流程如下:
   第一步:获取类型对象的类型对象,说白了就是元类,执行元类的 tp_call 指向的函数,即 type_call
   第二步:type_call 会调用该类型对象的 tp_new 指向的函数,如果 tp_new 为 NULL,那么会到 tp_base 指定的父类里面去寻找 tp_new。在新式类当中,所有的类都继承自 object,因此最终会执行 object 的 __new__。然后通过访问对应类型对象中的 tp_basicsize 信息,这个信息记录着该对象的实例对象需要占用多大的内存,继而完成申请内存的操作
   调用type_new 创建完对象之后,就会进行实例对象的初始化,会将指向这片空间的指针交给 tp_init,但前提是 tp_new 返回的实例对象的类型要一致。
  所以都说 Python 在实例化的时候会先调用 __new__ 方法,再调用 __init__ 方法,相信你应该知道原因了,因为在源码中先调用 tp_new、再调用的 tp_init。
  1.   static PyObject *
  2.   type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
  3.   {  
  4.      //调用__new__方法, 拿到其返回值
  5.      obj = type->tp_new(type, args, kwds);
  6.      if (type->tp_init != NULL) {
  7.          //将__new__返回的实例obj,和args、kwds组合起来
  8.          //一起传给 __init__
  9.          //其中 obj 会传给 self,
  10.          int res = type->tp_init(obj, args, kwds);
  11.          //......
  12.      return obj;
  13.   }
复制代码
  所以源码层面表现出来的,和我们在 Python 层面看到的是一样的。
  小结
  到此,我们就从 Python 和解释器两个层面了解了对象是如何调用的,更准确的说我们是从解释器的角度对 Python 层面的知识进行了验证,通过 tp_new 和 tp_init 的关系,来了解 __new__ 和 __init__ 的关系。
  另外,对象调用远不止我们目前说的这么简单,更多的细节隐藏在了幕后,只不过现在没办法将其一次性全部挖掘出来。

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|湖南新梦想 ( 湘ICP备18019834号-2 )

GMT+8, 2022-8-16 03:55 , Processed in 0.040365 second(s), 19 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表