起步
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
.这些函数指针可以视为类型对象中所定义的操作, 而这些操作直接决定着一个对象在运行时所表现的行为.
PyTypeObject
中 tp_hash
指明对于该类型的对象, 如何生成hash值. tp_hash的类型是 hashfunc
而实际上是要给函数指针:
typedef Py_hash_t (*hashfunc)(PyObject *);
#Py_hash_t 理解为int即可.
在对象的创建中, tp_new
和 tp_init
决定了一个实例对象呗创建和初始化的. 另外,在 PyTypeObject
中有三个很重要的操作族: tp_as_number
, tp_as_sequence
和 tp_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类型变量的类型对象指针都是指向同一个, 且不会被视为是类型对象的引用.