Python内核阅读(二): 对象的创建

Python 2017-08-01

起步

python提供两种来创建对象, 一种是C API, 第二种通过类型对象创建,如 PyLong_Type .

在C API中也有两类, 一类是泛型的API,形式如 PyObject_xxx ,可以应用在任何python对象上, PyObject_Print([int obj]|[string obj]) .参数可以任意类型,其API内部自己确定最终调用的函数是哪一个.

另一类与类型相关的API,如:

PyObject *a = PyLong_FromLong(10);

不论采用哪种C API,最终都是直接分配内存.

对象的行为

PyTypeObject 中定义了大量的函数指针, 这些指针最终都会指向某个函数或者指向 NULL .这些函数指针可以视为类型对象中所定义的操作, 而这些操作直接决定着一个对象在运行时所表现的行为.

PyTypeObjecttp_hash 指明对于该类型的对象, 如何生成hash值. tp_hash的类型是 hashfunc 而实际上是要给函数指针:

typedef Py_hash_t (*hashfunc)(PyObject *);
#Py_hash_t 理解为int即可.

在对象的创建中, tp_newtp_init 决定了一个实例对象呗创建和初始化的. 另外,在 PyTypeObject 中有三个很重要的操作族: tp_as_number, tp_as_sequencetp_as_mapping.

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

以其中tp_as_number为例

typedef struct {
    binaryfunc nb_add;
    binaryfunc nb_subtract;
    ...
} PyNumberMethods;

定义了作为一个数值对象支持的操作. 如果一个对象被视为数值对象(如整型), 那么它的 tp_as_number.nb_add 就要有对该对象进行加法操作的具体行为.PySequenceMethods和PyMappingMethods比较典型的例子就是list和dict.

类型的类型

PyTypeObject 的结构中也有 PyObject_VAR_HEAD , 这意味着python的类型也是一个对象, 那么有一个问题就是类型对象的类型是什么?

[typeobject.c]
PyTypeObject PyType_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "type",                                     /* tp_name */
    sizeof(PyHeapTypeObject),                   /* tp_basicsize */
    ...
};

也就是说类型对象将类型指向了自己

对象的多态性

python的通过对象的 ob_type 域动态进行判断, 例如里面中 tp_print 的属性就可以这样利用:

void Print(PyObject* object) {
    object->ob_type->tp_print(object);
}

函数之间一般是通过泛型指针 PyObject* 来传递的.

这样的C API建立在这多态机制上:

Py_hash_t PyObject_Hash(PyObject *v)
{
    PyTypeObject *tp = Py_TYPE(v);
    if (tp->tp_hash != NULL)
        return (*tp->tp_hash)(v);
    ...
}

引用计数

在C/C++中,程序员有极大的自由,他们可以任意申请内存,但是程序员也要负责内存释放, 毕竟能力越大,责任越大.也是这一点,大量内存泄露和空指针的bug诞生,从此黄河泛滥一发不可收拾.

因此在现代开发语言中,索性由语言本身负责内存管理和维护,自有的垃圾回收机制比如java,C#, 使开发人员不需要进行繁重的内存管理工作.

python内建了垃圾回收机制, 引用计数是其中的一部分.

源码中, 通过 Py_INCREF(op)Py_DECREF(op) 两个宏来增加和较少引用计数的.

[object.h]

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

#define Py_DECREF(op)                                   \
    do {                                                \
        PyObject *_py_decref_tmp = (PyObject *)(op);    \
        if (--(_py_decref_tmp)->ob_refcnt != 0)             \
            _Py_CHECK_REFCNT(_py_decref_tmp)            \
        else                                            \
            _Py_Dealloc(_py_decref_tmp);                \
    } while (0)

当一个对象的引用计数减少到0后,Py_DECREF(op)会调用该对象"析构函数"进行释放.

#define _Py_Dealloc(op) (                               \
(*Py_TYPE(op)->tp_dealloc)((PyObject *)(op)))

当ob_refcnt为0时触发对象销毁, 隐隐有点像设计模式中观察者模式的味道.

有意思的是, 类型对象死超越引用计数规则的, 它永远不会被析构.并且类型对象是共享的, 多个int类型变量的类型对象指针都是指向同一个, 且不会被视为是类型对象的引用.


本文由 hongweipeng 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

如果对您有用,您的支持将鼓励我继续创作!