Python培训
400-996-5531
都说可以自己造轮子的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培训机构进行咨询。
免责声明:内容和图片源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。
填写下面表单即可预约申请免费试听! 怕学不会?助教全程陪读,随时解惑!担心就业?一地学习,可全国推荐就业!
Copyright © 京ICP备08000853号-56 京公网安备 11010802029508号 达内时代科技集团有限公司 版权所有
Tedu.cn All Rights Reserved