实例对象的生命周期

一. 泛型API和特型API

Python由C语言实现,对外提供C API目的是在C环境中实现交互。C API分为泛型API特型API两类。

1.1 泛型API

泛型API 与类型无关,属于抽象对象层(Abstract Object Layer,AOL)。这类API参数是PyObject*,可处理任意类型的对象,API 内部根据对象类型区别处理

以打印对象的方法为例,

int
PyObject_Print(PyObject *op, FILE *fp, int flags)

方法第一个参数是任意类型的待打印对象,参数类型是PyObject*。Python 底层一般都是通过PyObject*引用达到泛型化的目的。

任意类型的对象,均可调用PyObject_Print打印,

// 打印浮点对象
PyObject *fo = PyFloatObject_FromDouble(3.14);
PyObject_Print(fo, stdout, 0);

// 打印整数对象
PyObject *lo = PyFloatObject_FromLong(100);
PyObject_Print(lo, stdout, 0);

接口内部根据对象类型决定如何输出。

1.2 特型API

特型API与类型相关,属于具体对象层(Concrete Object Layer,COL)。这类API只能作用于某种类型的对象,Python内部为每一种内置对象提供了这样一组API。

如创建一个浮点数对象,

PyObject *
PyFloat_FromDouble(double fval)

二. 对象的创建

对象的元数据保存在对应的类型对象中,元数据中包含对象如何创建的信息。因此,实例对象由类型对象创建

2.1 创建对象的方式

Python内部一般通过这两种方法创建对象,

  • 通过C API,例如PyFloat_FromDouble在接口内部为PyFloatObject结构体分配内存,并初始化相关字段。多用于内建类型
  • 通过类型对象,例如自定义Dog类需要分配多少内存,如何进行初始化,需要在类型对象中找。多用于自定义类型

2.2 创建对象的过程

2.1节中第二种通过类型对象创建实例对象的方法更为通用,同时支持内置类型和自定义类型。以通过浮点类型PyFloat_Type创建浮点数对象为例,

>>> pi = float('3.14')
>>> pi
3.14

通过调用类型对象float,实例化一个浮点实例pi。在Python中,可以被调用的对象就是可调用对象。类型对象float的类型对象是type,当float被调用时,执行的方法一定存在于type的实体中,对应可以找到PyType_Type中的tp_call字段,该字段是一个函数指针。

PyTypeObject PyType_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "type",                                     /* tp_name */
    sizeof(PyHeapTypeObject),                   /* tp_basicsize */
    sizeof(PyMemberDef),                        /* tp_itemsize */

    // ...
    (ternaryfunc)type_call,                     /* tp_call */

    // ...
};

Python代码中,float(‘3.14’)在C层面等价于

PyFloat_Type.ob_type.tp_call(&PyFloat_Type, args, kwargs)
					|
					∨
PyType_Type.tp_call(&PyFloat_Type, args, kwargs)

args和kwargs参数先不介绍,详见函数机制部分内容。
type_call方法,定义于Include/typeobject.c,关键代码如下,

static PyObject *
type_call(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    PyObject *obj;

    // ...
    obj = type->tp_new(type, args, kwds);
    obj = _Py_CheckFunctionResult((PyObject*)type, obj, NULL);
    if (obj == NULL)
        return NULL;

    // ...
    type = Py_TYPE(obj);
    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;
}
  • 调用类型对象tp_new函数指针申请内存(第 7 行);
  • 必要时调用类型对象tp_init函数指针对对象进行初始化(第 15 行);

Alt

  1. 调用float,最终执行其类型对象typetp_call方法
  2. tp_call方法调用floattp_new函数为实例对象分配内存空间
  3. tp_call函数必要时进一步调用tp_init函数对实例对象进行初始化

三. 对象的多态性

3.1 Python中多态的应用

多态可以简单的理解为父类的引用指向子类对象。

Python创建一个对象,比如PyFloatObject,会分配内存并初始化。此后,Python内部统一通过一个PyObject*变量来保存和维护这个对象,而不是通过PyFloatObject*变量。这样可以实现更抽象的上层逻辑,而不用关心对象的实际类型和实现细节。

以计算对象哈希值为例,

Py_hash_t
PyObject_Hash(PyObject *v)
{
    PyTypeObject *tp = Py_TYPE(v);
    if (tp->tp_hash != NULL)
        return (*tp->tp_hash)(v);
    /* To keep to the general practice that inheriting
    * solely from object in C code should work without
    * an explicit call to PyType_Ready, we implicitly call
    * PyType_Ready here and then check the tp_hash slot again
    */
    if (tp->tp_dict == NULL) {
        if (PyType_Ready(tp) < 0)
            return -1;
        if (tp->tp_hash != NULL)
            return (*tp->tp_hash)(v);
    }
    /* Otherwise, the object can't be hashed */
    return PyObject_HashNotImplemented(v);
}
  • 第4行先通过ob_type指针找到对象的类型
  • 第6行通过类型对象的tp_hash函数指针,调用对应的哈希值计算函数

3.2 对象行为

不同对象的行为不同,比如哈希值计算方法就不同,由类型对象的tp_hash字段决定。

除了tp_hashPyTypeObject结构体还定义了很多函数指针这些指针最终都会指向某个函数,或者为空。 这些函数指针可以看做是类型对象中定义的操作,这些操作决定对应实例对象在运行时的行为。

Python 依据对象所属的类别,为每个类别都定义了一个标准操作集,每个操作集中包含了多个函数指针,
Alt
标准操作集被定义在PyTypeObject结构体中,

typedef struct _typeobject {
    PyObject_VAR_HEAD
    const char *tp_name; /* For printing, in format "<module>.<name>" */
    Py_ssize_t tp_basicsize, tp_itemsize; /* For allocation */

    // ...
    /* Method suites for standard classes */

    PyNumberMethods *tp_as_number;
    PySequenceMethods *tp_as_sequence;
    PyMappingMethods *tp_as_mapping;

    // ...
    /* Functions to access object as input/output buffer */
    PyBufferProcs *tp_as_buffer;

    // ...
} PyTypeObject;
函数操作集说明
PyNumberMethods结构体定义了 数值型 操作
PySequenceMethods结构体定义了 序列型 操作
PyMappingMethods结构体定义了 关联型 操作

以类型对象float为例,其对应的PyFloat_Type结构体及对应的函数操作集如下,

static PyNumberMethods float_as_number = {
    float_add,          /* nb_add */
    float_sub,          /* nb_subtract */
    float_mul,          /* nb_multiply */
    float_rem,          /* nb_remainder */
    float_divmod,       /* nb_divmod */
    float_pow,          /* nb_power */
    // ...
};

PyTypeObject PyFloat_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "float",
    sizeof(PyFloatObject),

    // ...
    &float_as_number,                           /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */

    // ...
};
  • 字段tp_as_number非空,因此float对象支持数值型操作
  • 字段tp_as_sequence为空,因此float对象 不支持序列型操作
  • 字段tp_as_mapping为空,因此float对象 不支持关联型操作

3.3 引用计数

垃圾回收机制的关键是对象的引用计数,每个Python对象都有一个ob_refcnt字段,记录着对象当前的引用计数。 当对象被其他地方引用时,ob_refcnt加一;当引用解除时,ob_refcnt减一。 当ob_refcnt为零,说明对象已经没有任何引用了,这时便可将其回收。通过sys模块的getrefcount方法可以获取某个对象当前的引用计数。

Python定义了两个非常重要的宏,用于维护对象应用计数。

#define Py_INCREF(op) (                         \
    _Py_INC_REFTOTAL  _Py_REF_DEBUG_COMMA       \
    ((PyObject *)(op))->ob_refcnt++)

#define Py_DECREF(op)                                   \
    do {                                                \
        PyObject *_py_decref_tmp = (PyObject *)(op);    \
        if (_Py_DEC_REFTOTAL  _Py_REF_DEBUG_COMMA       \
        --(_py_decref_tmp)->ob_refcnt != 0)             \
            _Py_CHECK_REFCNT(_py_decref_tmp)            \
        else                                            \
            _Py_Dealloc(_py_decref_tmp);                \
    } while (0)
  • Py_INCREF的第3行将对象应用计数加一
  • Py_DECREF的第5行将引用计数减一,并在第8行判断引用计数为0时回收对象

当一个对象引用计数为0, Python调用对象对应的析构函数销毁对象,但这并不意味着对象内存一定会回收。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值