python扩展学习->使用python/c api实现一个简单的单链表

我们都知道python的执行效率相较于其他比较出名的语言是比较低的,但是因为开发效率高加上简单易学,让其成为比较流行的编程语言,而对于一个项目来说,一般只有20%的性能瓶颈是在语言层面的,我们只有找到这20%的性能瓶颈,用执行效率比较高的c去实现就行了,好在CPython提供了c/api,方便我们用c去扩展python。本例子主要是用于学习python c/api(案例代码地址:https://github.com/marskang/python-ext-linklist.git)。

链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。既然我们要写单链表,首先要实现的是单链表的数据结构,我们可以按照教科书上面的链表结构来定义:

typedef struct LinkList {
     int Element;
     struct LinkList * next;
} LinkList;

这样一个单链表的数据结构虽然定义好了,但是因为我们是用c去扩展python,这样的数据结构,python解释器并不能识别,我们需要将该数据结构改为能够转为python对象的数据结构。可以参考官方的例子https://docs.python.org/2/extending/newtypes.html?highlight=noddy
来写。首先需要在数据结构的第一行加入PyObject_HEAD宏,因为我们的数据结构是可以像list一样放进去任何python对象的,所以我们需要将int转改为PyObject:

typedef struct PyLinkList_Node {
    PyObject_HEAD
    PyObject* content;
    struct PyLinkList_Node* next;
} PyLinkList_Node;

这样一个简单的python的单链表的数据结构就定义好了,但是我们由于的链表还有一些添加,获取长度,迭代等方法,所以最终的结构如下:

typedef struct PyLinkList_Node {
    PyObject_HEAD
    Py_ssize_t count;
    PyObject* content;
    struct PyLinkList_Node* cursor;
    struct PyLinkList_Node* next;
    struct PyLinkList_Node* tail;
} PyLinkList_Node;

这样虽然数据结构定义好了,但是python解释器还是没办法将其转为python对象,我们可以还需要定义PyTypeObject才行,我们单链表的PyTypeObject实现如下:

PyTypeObject pyLinkList_NodeType = {
        PyVarObject_HEAD_INIT(NULL, 0)
        "PyLinkList",                                   /* tp_name */
        sizeof(PyLinkList_Node),                        /* tp_basicsize */
        0,                                              /* tp_itemsize */
        (destructor)pyLinkList_dealloc,                 /* tp_dealloc */
        0,                                              /* tp_print */
        0,                                              /* tp_getattr */
        0,                                              /* tp_setattr */
        0,                                              /* tp_reserved */
        0,                                              /* tp_repr */
        0,                                              /* tp_as_number */
        &linklist_as_sequence,                          /* tp_as_sequence */
        0,                                              /* tp_as_mapping */
        0,                                              /* tp_hash */
        0,                                              /* tp_call */
        0,                                              /* tp_str */
        0,                                              /* tp_getattro */
        0,                                              /* tp_setattro */
        0,                                              /* tp_as_buffer */
        Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,         /* tp_flags */
        0,                                              /* tp_doc */
        0,                                              /* tp_traverse */
        0,                                              /* tp_clear */
        0,                                              /* tp_richcompare */
        0,                                              /* tp_weaklistoffset */
        (getiterfunc)pyLinkList_getiter,                /* tp_iter */
        (iternextfunc)pyLinkList_iternext,              /* tp_iternext */
        pyLinkList_methods,                             /* tp_methods */
        0,                                              /* tp_members */
        0,                                              /* tp_getset */
        0,                                              /* tp_base */
        0,                                              /* tp_dict */
        0,                                              /* tp_descr_get */
        0,                                              /* tp_descr_set */
        0,                                              /* tp_dictoffset */
        (initproc)pyLinkList_init,                      /* tp_init */
        0,                                              /* tp_alloc */
        0,                                              /* tp_new */
        0                                               /* tp_free */
};

这样一个单链表的数据类型就定义好了,当链表实例化的时候,会调用pyLinkList_init方法,返回一个头节点的指针。使用for循环遍历的时候会先调用pyLinkList_getiter,然后每次再调用pyLinkList_iternext,直到返回NULL,我们是链表,我们希望用数组下标的方式来访问第n个元素,那当我使用数组下标访问链表元素的时候,PyTypeObject会先找到一开始定义好的linklist_as_sequence,然后找到对应的方法。对象被回收时,会调用pyLinkList_dealloc方法,pyLinkList_methods是定义好的对象的方法。

我们要实现的是单链表,单链表首先要实现的是能够添加数据吧,所以我们需要先实现一个append方法。

void pyLinkList_append(PyLinkList_Node* self, PyObject* obj) {
    PyLinkList_Node* node = (PyLinkList_Node*) (pyLinkList_NodeType.tp_alloc(&pyLinkList_NodeType, 0));
    if (!node) {
        return;
    }
    Py_INCREF(self);
    node->content = obj;
    node->next = NULL;
    self->tail->next = node;
    self->tail = node;
    self->count++;
}

方法定义好了,我们需要要将其跟PyLinkList_Node关联起来,我们需要先定义个PyMethodDef数组将所有的方法与其关联起来。

static PyMethodDef pyLinkList_methods[] = {
    {"append", (PyCFunction)pyLinkList_append, METH_O, "append item"},
    {NULL},
};

每一个数组元素代表一个元素,以一组都是NULL的结束,第一个元素代表方法名(我们这里是append),第二个是c实现的方法,第三个是参数类型(我们这里是METH_O,代表传的是一个python object,再比如METH_NOARGS代表没有参数,具体的可以参考api),最后一个是方法的doc。然后将pyLinkList_methods添加到PyTypeObject即可,我们上面实现的PyTypeObject已将其添加过了。

既然是一个链表的数据结构,我们希望能够将其放入for循环中去迭代,首先我们需要先实现getiterfunc,返回链表的头节点地址,让其变成一个可迭代的对象。

PyObject* pyLinkList_getiter(PyLinkList_Node* self) {
    Py_INCREF(self);
    return (PyObject*)self;
}

然后pyLinkList_getiter放入PyTypeObject结构中。

然后需要实现一个iternextfunc,是其每次迭代调的方法,直到返回NULL,迭代结束。

PyObject* pyLinkList_iternext(PyLinkList_Node* self) {
    PyLinkList_Node* next;
    if(!self->next || !self->cursor) {
        //将游标重置
        self->cursor = self;
        return NULL;
    }
    if (self == self->cursor) {
        // 过滤头节点
        self->cursor = self->cursor->next;
        next = self->next;
    } else {
        next = self->cursor;
    }
    self->cursor = self->cursor->next;
    return next->content;
}

同样需要将其放入PyTypeObject中。
我们既然是一个链表,有时候我们想通过数组下标的的形式,来访问链表的第n个元素,这里我们需要先定义好PySequenceMethods:

static PySequenceMethods linklist_as_sequence = {
    (lenfunc)PyLinkList_count,                       /* sq_length */
    0,                                               /* sq_concat */
    0,                                               /* sq_repeat */
    (ssizeargfunc)pyLinkList_item,                   /* sq_item */
    0,                                               /* sq_slice */
    0,                                               /* sq_ass_item */
    0,                                               /* sq_ass_slice */
    0,                                               /* sq_contains */
    0,                                               /* sq_inplace_concat */
    0,                                               /* sq_inplace_repeat */
};

这里只是先实现了,sq_length(通过len方法调用,例如len(l)),跟sq_item(通过数组下标调用,例如l[0]),两个方法如下:

Py_ssize_t PyLinkList_count(PyLinkList_Node* self) {
    return self->count;
}

static PyObject *indexerr = NULL;
PyObject* pyLinkList_item(PyLinkList_Node* self, Py_ssize_t index) {
    if (index < 0 || index >= self->count) {
        if (indexerr == NULL) {
            indexerr = PyString_FromString("link list index out of range");
            if (indexerr == NULL)
                return NULL;
        }
        //返回数组越界异常
        PyErr_SetObject(PyExc_IndexError, indexerr);
        return NULL;
    }
    Py_ssize_t start = 0;
    //过滤头节点
    PyLinkList_Node* p = self->next;
    while(p) {
        if (start == index) {
            Py_INCREF(p->content);
            return p->content;
        }
        p = p->next;
        start++;
    }
    return NULL;
}

这样我们方法都写好了,但是我们如何让其变成python的模块跟python对象呢,首先我们还需要继续实现PyMethodDef数组,定义模块的方法,由于我们本模块没有单独的方法,只有一个链表类型(方法都是跟链表对象绑定的),所以数组只有一组为NULL的元素。

static PyMethodDef pyLinkList_module_methods[] = {
    {NULL},
};

然后再实现一个PyMODINIT_FUNC的方法,我们这里模块命名为linklist,所以方法名需要为initlinklist(init+模块名):

PyMODINIT_FUNC initlinklist(void) {
    pyLinkList_NodeType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&pyLinkList_NodeType) < 0) {
        return;
    }
    PyObject* m = Py_InitModule3("linklist", pyLinkList_module_methods, "test");
    Py_INCREF(&pyLinkList_NodeType);
    //给模块添加一个python类型,类型名为LinkList
    PyModule_AddObject(m, "LinkList", (PyObject*)&pyLinkList_NodeType);
}

这样我们的代码就编写完成了,下面就是编译链接执行了,有两种方法,一种是通过gcc编译成so调用:

gcc -shared -fPIC link_list.c -I/usr/include/python2.7/ -L/usr/lib -lpython2.7 -o linklist.so

还有一种是通过distutils,将其加入python库使用,首先需要新建一个setup.py文件,内容如下:

from distutils.core import setup, Extension

setup(name="linklist", version="1.0",ext_modules=[Extension("linklist", ["link_list.c"]),])

之后我们就可以使用c实现的单链表了:

# -*- coding: utf-8 -*-

import linklist

l = linklist.LinkList()
l.append("张三")
l.append("12")
l.append(666)

for i in l:
    print i

print len(l)
print l[1]

输入结果如下:

$ python test.py 
张三
12
666
3
12

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值