PyObject多态的实现

PyObject的多态实现

PyObject 是 Python 所有对象的基石,所有其他对象 PyIntObjectPyFloatObject 的行为,当强制转换为 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;

可以看出 PyIntObjectPyFloatObject 第一个定义的内容都是 PyObject_HEAD, 所以这两个结构体的前两个成员也是

  • ob_refcnt
  • ob_type

由于结构体访问成员是通过地址的偏移访问的,而 PyIntObjectPyObjectob_refcntob_typ 的相对位置是一样的,所以当我们把一个 PyintObject* 转成 PyObject* 时,我们可以通过 PyObject* 指针正确拿到 ob_refcntob_type 。这点很重要,因为包含子类的信息在这个 ob_type 里面,如果我们不能通过 PyObject* 拿到子类的信息,多态便无法实现。
我们知道,C++中的多态是通过虚表指针来实现的,而C++的虚表指针是在类内存布局中的最前面,因此我们拿到一个基类的指针,也就可以通过固定的偏移拿到虚函数表,也就可以找到子类对应的实现细节了。可以看出,C++的多态和CPython实现的多态有异曲同工之妙。

PyIntObject intObject;
PyObject* object = (PyObject*)(&intObject);
object.ob_type; // 可正确得到结果,因为地址偏移是一样的

然后 PyIntObjectPyFloatObjectob_type 是不一样的

  • PyIntObjectob_typePyInt_Type
  • PyFloatObjectfob_typePyFloat_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_Typeprintfunc 指向的是 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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1、简述编译型和解释型语言编译型:运行前先由编译器将高级语言代码编译为对应机器的cpu汇编指令集,再由汇编器汇编为...
    Zzmi阅读 1,015评论 0 0
  • 包(lib)、模块(module) 在Python中,存在包和模块两个常见概念。 模块:编写Python代码的py...
    清清子衿木子水心阅读 3,834评论 0 27
  • 申明:这里整理的面试题题目主要来源于网络搜集,答案根据自己实践整理修改。 1. 简述解释型语言和编译型语言区别 解...
    寻觅的以诺阅读 456评论 0 0
  • Python语言进阶 数据结构和算法 算法:解决问题的方法和步骤 评价算法的好坏:渐近时间复杂度和渐近空间复杂度。...
    you的日常阅读 584评论 2 8
  • 5月以来,哪怕对市场风向再不敏感的人,也感觉到阵阵凉意。二级市场连续下挫,一级市场融资环境恶化,不论企业融资数量还...
    钱皓频道阅读 6,119评论 1 6