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

湖南新梦想

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

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

[复制链接]

3089

主题

3489

帖子

1万

积分

论坛元老

Rank: 8Rank: 8

积分
12350
发表于 2022-6-28 15:39:48 | 显示全部楼层 |阅读模式
本帖最后由 chaomeili 于 2022-6-28 15:41 编辑

 从解释器的角度看对象的调用
  我们以内置类型 float 为例,我们说创建一个 PyFloatObject,可以通过3.14或者float(3.14)的方式。前者使用Python/C API创建,3.14直接被解析为 C 一级数据结构,也就是PyFloatObject实例;后者使用类型对象创建,通过对float进行一个调用、将3.14作为参数,最终也得到指向C一级数据结构PyFloatObject实例。
  Python/C API的创建方式我们已经很清晰了,就是根据值来推断在底层应该对应哪一种数据结构,然后直接创建即可。我们重点看一下通过类型调用来创建实例对象的方式。
  如果一个对象可以被调用,它的类型对象中一定要有tp_call(更准确的说成员tp_call的值是一个函数指针,不可以是0),而PyFloat_Type是可以调用的,这就说明PyType_Type内部的tp_call是一个函数指针,这在Python的层面上我们已经验证过了,下面我们再来通过源码看一下。
  1.   //typeobject.c
  2.   PyTypeObject PyType_Type = {
  3.      PyVarObject_HEAD_INIT(&PyType_Type, 0)
  4.      "type",                                     /* tp_name */
  5.      sizeof(PyHeapTypeObject),                   /* tp_basicsize */
  6.      sizeof(PyMemberDef),                        /* tp_itemsize */
  7.      (destructor)type_dealloc,                   /* tp_dealloc */
  8.      //...                                       /* tp_hash */
  9.      (ternaryfunc)type_call,                     /* tp_call */
  10.      //...
  11.   }
复制代码

  我们看到在实例化PyType_Type的时候PyTypeObject内部的成员tp_call被设置成了type_call。这是一个函数指针,当我们调用PyFloat_Type的时候,会触发这个type_call指向的函数。
  因此 float(3.14) 在C的层面上等价于:
  1.   (&PyFloat_Type) -> ob_type -> tp_call(&PyFloat_Type, args, kwargs);
  2.   // 即:
  3.   (&PyType_Type) -> tp_call(&PyFloat_Type, args, kwargs);
  4.   // 而在创建 PyType_Type 的时候,给 tp_call 成员传递的是 type_call
  5.   // 因此最终相当于
  6.   type_call(&PyFloat_Type, args, kwargs)
复制代码

  如果用 Python 来演示这一过程的话:
  1.   # float(3.14),等价于
  2.   f1 = float.__class__.__call__(float, 3.14)
  3.   # 等价于
  4.   f2 = type.__call__(float, 3.14)
  5.   print(f1, f2)  # 3.14 3.14
复制代码

  这就是 float(3.14) 的秘密,相信list、dict在实例化的时候是怎么做的,你已经猜到了,做法是相同的。
  1.   # lst = list("abcd")
  2.   lst = list.__class__.__call__(list, "abcd")
  3.   print(lst)  # ['a', 'b', 'c', 'd']
  4.   # dct = dict([("name", "古明地觉"), ("age", 17)])
  5.   dct = dict.__class__.__call__(dict, [("name", "古明地觉"), ("age", 17)])
  6.   print(dct)  # {'name': '古明地觉', 'age': 17}
复制代码

  最后我们来围观一下 type_call 函数,我们说 type 的 __call__ 方法,在底层对应的是 type_call 函数,它位于Object/typeobject.c中。
  1.   static PyObject *
  2.   type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
  3.   {  
  4.      // 如果我们调用的是 float
  5.      // 那么显然这里的 type 就是 &PyFloat_Type
  6.      // 这里是声明一个PyObject *
  7.      // 显然它是要返回的实例对象的指针
  8.      PyObject *obj;
  9.      // 这里会检测 tp_new是否为空,tp_new是什么估计有人已经猜到了
  10.      // 我们说__call__对应底层的tp_call
  11.      // 显然__new__对应底层的tp_new,这里是为实例对象分配空间
  12.      if (type->tp_new == NULL) {
  13.          // tp_new 是一个函数指针,指向具体的构造函数
  14.          // 如果 tp_new 为空,说明它没有构造函数
  15.          // 因此会报错,表示无法创建其实例
  16.          PyErr_Format(PyExc_TypeError,
  17.                       "cannot create '%.100s' instances",
  18.                       type->tp_name);
  19.          return NULL;
  20.      }
  21.      //通过tp_new分配空间
  22.      //此时实例对象就已经创建完毕了,这里会返回其指针
  23.      obj = type->tp_new(type, args, kwds);
  24.      //类型检测,暂时不用管
  25.      obj = _Py_CheckFunctionResult((PyObject*)type, obj, NULL);
  26.      if (obj == NULL)
  27.          return NULL;
  28.      //我们说这里的参数type是类型对象,但也可以是元类
  29.      //元类也是由PyTypeObject结构体实例化得到的
  30.      //元类在调用的时候执行的依旧是type_call
  31.      //所以这里是检测type指向的是不是PyType_Type
  32.      //如果是的话,那么实例化得到的obj就不是实例对象了,而是类型对象
  33.      //要单独检测一下
  34.      if (type == &PyType_Type &&
  35.          PyTuple_Check(args) && PyTuple_GET_SIZE(args) == 1 &&
  36.          (kwds == NULL ||
  37.           (PyDict_Check(kwds) && PyDict_GET_SIZE(kwds) == 0)))
  38.          return obj;
  39.      //tp_new应该返回相应类型对象的实例对象(的指针)
  40.      //但如果不是,就直接将这里的obj返回
  41.      //此处这么做可能有点难理解,我们一会细说
  42.      if (!PyType_IsSubtype(Py_TYPE(obj), type))
  43.          return obj;
  44.      //拿到obj的类型
  45.      type = Py_TYPE(obj);
  46.      //执行 tp_init
  47.      //显然这个tp_init就是__init__函数
  48.      //这与Python中类的实例化过程是一致的。
  49.      if (type->tp_init != NULL) {
  50.          //将tp_new返回的对象作为self,执行 tp_init
  51.          int res = type->tp_init(obj, args, kwds);
  52.          if (res < 0) {
  53.              //执行失败,将引入计数减1,然后将obj设置为NULL
  54.              assert(PyErr_Occurred());
  55.              Py_DECREF(obj);
  56.              obj = NULL;
  57.          }
  58.          else {
  59.              assert(!PyErr_Occurred());
  60.          }
  61.      }
  62.      //返回obj
  63.      return obj;
  64.   }
复制代码
  因此从上面我们可以看到关键的部分有两个:
  ·调用类型对象的 tp_new 指向的函数为实例对象申请内存
  · 调用 tp_init 指向的函数为实例对象进行初始化,也就是设置属性
  所以这对应Python中的__new__和__init__,我们说__new__是为实例对象开辟一份内存,然后返回指向这片内存(对象)的指针,并且该指针会自动传递给__init__中的self。
  1.   class Girl:
  2.      def __new__(cls, name, age):
  3.          print("__new__方法执行啦")
  4.          # 写法非常固定
  5.          # 调用object.__new__(cls)就会创建Girl的实例对象
  6.          # 因此这里的cls指的就是这里的Girl,注意:一定要返回
  7.          # 因为__new__会将自己的返回值交给__init__中的self
  8.          return object.__new__(cls)
  9.      def __init__(self, name, age):
  10.          print("__init__方法执行啦")
  11.          self.name = name
  12.          self.age = age
  13.   g = Girl("古明地觉", 16)
  14.   print(g.name, g.age)
  15.   """
  16.   __new__方法执行啦
  17.   __init__方法执行啦
  18.   古明地觉 16
  19.   """
复制代码
  __new__里面的参数要和__init__里面的参数保持一致,因为我们会先执行__new__,然后解释器会将__new__的返回值和我们传递的参数组合起来一起传递给__init__。因此__new__里面的参数除了cls之外,一般都会写*args和**kwargs。
  然后再回过头来看一下type_call中的这几行代码:
  1.   static PyObject *
  2.   type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
  3.   {  
  4.      //......
  5.      //......
  6.      if (!PyType_IsSubtype(Py_TYPE(obj), type))
  7.          return obj;
  8.      //......
  9.      //......
  10.   }
复制代码
  我们说tp_new应该返回该类型对象的实例对象,而且一般情况下我们是不写__new__的,会默认执行。但是我们一旦重写了,那么必须要手动返回object.__new__(cls)。可如果我们不返回,或者返回其它的话,会怎么样呢?


回复

使用道具 举报

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

本版积分规则

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

GMT+8, 2022-8-15 12:17 , Processed in 0.041679 second(s), 19 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2020, Tencent Cloud.

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