Python培训
美国上市Python培训机构

400-111-8989

热门课程

学会使用Cython扩展python的技巧,你就能自己造轮子!

  • 发布:Nugine
  • 来源:Python中文社区
  • 时间:2018-06-21 17:29

都说可以自己造轮子的python工程师才是牛人,今天就教你学会使用Cython扩展python的技巧,然后你就能自己造轮子了,兴不兴奋?如果你同时有 C/C++和 Python 的编码能力,我相信你对今天的分享会更感兴趣!

我们要造的轮子是一个最简单的栈的实现,用 C/C++来编写能够减小不必要的开销,带来显著的加速。

步骤

一、建立目录

二、编写 C++文件

三、编写 pyx 文件

四、直接编译

五、测试

1. 建立目录

首先,建立我们的工作目录。

mkdir pystack

cd pystack

32 位版本和 64 位版本会带来不同的问题。我的 C 库是 32 位的,所以 python 库必须也是 32 位。

使用 pipenv 指定 python 版本,并安装 Cython。

pipenv --python P:\Py3.6.5\python.exe

pipenv install Cython

2. 编写 C++文件

按 Python 官方文档,这里 C++必须用 C 的方式编译,所以需要加上 extern "C"。

"c_stack.h"

#include "python.h"

extern "C"{

class C_Stack {

private:

struct Node {

PyObject* val;

Node* prev;

};

Node* tail;

public:

C_Stack();

~C_Stack();

PyObject* peek();

void push(PyObject* val);

PyObject* pop();

};

}

"c_stack.cpp"

extern "C"{

#include "c_stack.h"

}

C_Stack::C_Stack() {

tail = new Node;

tail->prev = NULL;

tail->val = NULL;

};

C_Stack::~C_Stack() {

Node *t;

while(tail!=NULL){

t=tail;

tail=tail->prev;

delete t;

}

};

PyObject* C_Stack::peek() {

return tail->val;

}

void C_Stack::push(PyObject* val) {

Node* nt = new Node;

t->prev = tail;

t->val = val;

tail = nt;

}

PyObject* C_Stack::pop() {

Node* ot = tail;

PyObject* val = tail->val;

if (tail->prev != NULL) {

tail = tail->prev;

delete ot;

}

return val;

}

最简单的栈实现,只有 push,peek,pop 三个接口,作为示例足够了。

3. 编写 pyx 文件

Cython 使用 C 与 Python 混合的语法简化了扩展 Python 的步骤。

编写起来十分简单,前提是事先了解它的语法。

"pystack.pyx"

# distutils: language=c++

# distutils: sources = c_stack.cpp

from cpython.ref cimport PyObject,Py_INCREF,Py_DECREF

cdef extern from 'c_stack.h':

cdef cppclass C_Stack:

PyObject* peek();

void push(PyObject* val);

PyObject* pop();

class StackEmpty(Exception):

pass

cdef class Stack:

cdef C_Stack _c_stack

cpdef object peek(self):

cdef PyObject* val

val=self._c_stack.peek()

if val==NULL:

raise StackEmpty

return <object>val

cpdef object push(self,object val):

Py_INCREF(val);

self._c_stack.push(<PyObject*>val);

return None

cpdef object pop(self):

cdef PyObject* val

val=self._c_stack.pop()

if val==NULL:

raise StackEmpty

cdef object rv=<object>val;

Py_DECREF(rv)

return rv

分为四个部分:

注释指定相应的 cpp 文件.

从 CPython 导入 C 符号:PyObject,PyINCREF,PyDECREF。

从"cstack.h"导入 C 符号: CStack,以及它的接口。

将其包装为 Python 对象。

注意点:

在 C 实现中,当栈为空时,返回了空指针。Python 实现中检查空指针,并抛出异常 StackEmpty.

PyObject* 和 object 并不等同,需要做类型转换。

push 和 pop 时要正确操作引用计数,否则会让 Python 解释器直接崩溃。一开始不知道这个,懵逼好久,偶然间看到报错与 gc 有关,才想到引用计数的问题。

4. 直接编译

pipenv run cythonize -a -i pystack.cpp

生成三个文件: pystack.cpp,pystack.html,pystack.cp36-win32.pyd

pyx 编译到 cpp,再由 C 编译器编译为 pyd。

html 是 cython 提示,指出 pyx 代码中与 python 的交互程度。

pyd 就是最终的 Python 库了。

5. 测试一下

"test.py"

from pystack import *

st=Stack()

print(dir(st))

try:

st.pop()

except StackEmpty as exc:

print(repr(exc))

print(type(st.pop))

for i in ['1',1,[1.0],1,dict(a=1)]:

st.push(i)

while True:

print(st.pop())

pipenv run python test.py

['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__',

'__ne__', '__new__', '__pyx_vtable__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', 'peek', 'pop', 'push']

<class 'list'>

{'a': 1}

1

[1.0]

1

1

Traceback (most recent call last):

File "test.py", line 13, in <module>

print(st.pop())

File "pystack.pyx", line 32, in pystack.Stack.pop

cpdef object pop(self):

File "pystack.pyx", line 36, in pystack.Stack.pop

raise StackEmpty

pystack.StackEmpty

与正常 Python 对象表现相同,完美!

6. 应用

pipenv run python test_polish_notation.py

from operator import add, sub, mul, truediv

from fractions import Fraction

from pystack import Stack

def main():

exp = input('exp: ')

val = eval_exp(exp)

print(f'val: {val}')

op_map = {

'+': add,

'-': sub,

'*': mul,

'/': truediv

}

def convert(exp):

for it in reversed(exp.split(' ')):

if it in op_map:

yield True, op_map[it]

else:

yield False, Fraction(it)

def eval_exp(exp):

stack = Stack()

for is_op, it in convert(exp):

if is_op:

left = stack.pop()

right = stack.pop()

stack.push(it(left, right))

else:

stack.push(it)

return stack.pop()

if __name__ == '__main__':

main()

# exp: + 5 - 2 * 3 / 4 7

# val: 37/7

本篇文章展示了最简单的Cython造轮子技巧,希望能为即将加入到python培训学习或者和已经进入python领域的伙伴们提供一块垫脚石,大家学会了吗?如果你还有python相关的问题,欢迎你来python培训机构进行咨询。

免责声明:内容和图片源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

免费预约企业总监级讲师试听课

怕钱不够?就业挣钱后再付学费!    怕学不会?0基础入学,达内定制课程!     担心就业?近12万家雇主企业,推荐名企就业!

上一篇:各个维度深入剖析python中整型不会溢出的问题
下一篇:重要|python中的安全漏洞应该如何修复?

掌握这八个python高效数据分析的技巧,python大神都会的!

python培训知识分享之爬虫原理

python培训分享:为什么推荐python开发人员使用Pipenv?

python应用|七夕快到了,教你用python去表白!

选择城市和中心
贵州省

广西省

海南省