PyObject的多态实现
PyObject 是 Python 所有对象的基石,所有其他对象 PyIntObject
和 PyFloatObject
的行为,当强制转换为 PyObject
时,都可以正确得到结果。
这就很类似于面向对象的多态。而 CPython 是 C 语言的,并没有面向对象的概念。所以本文就从 CPython 的源码,简单分析下 PyObject
多态的实现原理
1. PyObject 的定义
#define PyObject_HEAD \
int ob_refcnt; \
struct _typeobject *ob_type;
typedef struct _object {
PyObject_HEAD
} PyObject;
PyObject结构体包含两个成员
- ob_refcnt: 引用计数,不在本文讨论范围
- ob_type: 每个对象的类型,这是实现多态的关键
2 PyIntObject 和 PyFloatObject 的定义
typedef struct {
PyObject_HEAD
double ob_fval;
} PyFloatObject;
typedef struct {
PyObject_HEAD
long ob_ival;
} PyIntObject;
可以看出 PyIntObject
和 PyFloatObject
第一个定义的内容都是 PyObject_HEAD
, 所以这两个结构体的前两个成员也是
- ob_refcnt
- ob_type
由于结构体访问成员是通过地址的偏移访问的,而 PyIntObject
和 PyObject
的 ob_refcnt
和 ob_typ
的相对位置是一样的,所以当我们把一个 PyintObject*
转成 PyObject*
时,我们可以通过 PyObject*
指针正确拿到 ob_refcnt
和 ob_type
。这点很重要,因为包含子类的信息在这个 ob_type
里面,如果我们不能通过 PyObject*
拿到子类的信息,多态便无法实现。
我们知道,C++中的多态是通过虚表指针来实现的,而C++的虚表指针是在类内存布局中的最前面,因此我们拿到一个基类的指针,也就可以通过固定的偏移拿到虚函数表,也就可以找到子类对应的实现细节了。可以看出,C++的多态和CPython实现的多态有异曲同工之妙。
PyIntObject intObject;
PyObject* object = (PyObject*)(&intObject);
object.ob_type; // 可正确得到结果,因为地址偏移是一样的
然后 PyIntObject
和 PyFloatObject
的 ob_type
是不一样的
-
PyIntObject
的ob_type
为PyInt_Type
-
PyFloatObjectf
的ob_type
为PyFloat_Type
而所有对象各自的行为都被封装在各自的类型中
PyTypeObject PyInt_Type = {
PyObject_HEAD_INIT(&PyType_Type)
0,
"int",
printfunc(print_int),
};
PyTypeObject PyFloat_Type = {
PyObject_HEAD_INIT(&PyType_Type)
0,
"float",
printfunc(print_float),
};
可以看出,PyInt_Type
的 printfunc
指向的是 print_int
, 而 PyFloat_Type
指向的是 print_float
, 所以
所以,我们可以通过 PyObject
拿到 ob_type
再去调用里面的东西,从而实现多态
// print 函数接收PyObject*对象,但是可以根据不同的子类实现不同的效果
void print(PyObject * obj)
{
obj->ob_type->tp_print(obj, NULL, 0);
}
int main()
{
PyIntObject intObject;
intObject.ob_ival = 1;
PyObject_INIT(&intObject, &PyInt_Type);
// 将 PyIntObject 转为 PyObject
PyObject *object1 = (PyObject*)(&intObject);
intObject.ob_type->tp_print(object1, NULL, 0);
PyFloatObject floatObject;
floatObject.ob_fval = 3.33;
PyObject_INIT(&floatObject, &PyFloat_Type);
// 将 PyFloatObject 转为 PyObject
PyObject *object2 = (PyObject*)(&floatObject);
floatObject.ob_type->tp_print(object2, NULL, 0);
return 0;
}
3 完整测试代码
#include "stdio.h"
#define PyObject_HEAD \
int ob_refcnt; \
struct _typeobject *ob_type;
#define PyObject_HEAD_INIT(type) \
1, type,
#define PyObject_VAR_HEAD \
PyObject_HEAD \
int ob_size;
typedef struct _object {
PyObject_HEAD
} PyObject;
typedef struct {
PyObject_VAR_HEAD
} PyVarObject;
typedef struct {
PyObject_HEAD
double ob_fval;
} PyFloatObject;
typedef struct {
PyObject_HEAD
long ob_ival;
} PyIntObject;
typedef int (*printfunc)(PyObject * , FILE * b, int c);
int print_float(PyObject *o, FILE * file, int i)
{
PyFloatObject *f = (PyFloatObject*)o;
printf("print float: val = %f\n", f->ob_fval);
return 0;
}
int print_int(PyObject *o, FILE * file, int a)
{
PyIntObject *i = (PyIntObject*)o;
printf("print int: val = %ld\n", i->ob_ival);
return 0;
}
typedef struct _typeobject {
PyObject_VAR_HEAD
const char *tp_name; /* For printing, in format "<module>.<name>" */
printfunc tp_print;
} PyTypeObject;
PyTypeObject PyType_Type = {
PyObject_HEAD_INIT(&PyType_Type)
0, /* ob_size */
"type", /* tp_name */
NULL
};
PyTypeObject PyInt_Type = {
PyObject_HEAD_INIT(&PyType_Type)
0,
"int",
(printfunc)(print_int),
};
PyTypeObject PyFloat_Type = {
PyObject_HEAD_INIT(&PyType_Type)
0,
"float",
(printfunc)(print_float),
};
/* Macros trading binary compatibility for speed. See also pymem.h.
Note that these macros expect non-NULL object pointers.*/
#define PyObject_INIT(op, typeobj) \
( (op)->ob_type = (typeobj), (PyObject *)(op), (op) )
void print(PyObject * obj)
{
obj->ob_type->tp_print(obj, NULL, 0);
}
int main()
{
PyIntObject intObject;
intObject.ob_ival = 1;
PyObject_INIT(&intObject, &PyInt_Type);
// 将 PyIntObject 转为 PyObject
PyObject *object1 = (PyObject*)(&intObject);
// 使用 print 打印
print(object1);
PyFloatObject floatObject;
floatObject.ob_fval = 3.33;
PyObject_INIT(&floatObject, &PyFloat_Type);
// 将 PyFloatObject 转为 PyObject
PyObject *object2 = (PyObject*)(&floatObject);
// 使用 print 打印
print(object2);
return 0;
}
打印如下
print int: val = 1
print float: val = 3.330000