“Gotham” by James Gilleard
♚
作者:Nugine
专栏地址:zhuanlan.zhihu.com/c_168195059
\
在本篇文章中,我要向你展示使用 Cython 扩展 Python 的技巧。
如果你同时有 C/C++和 Python 的编码能力,我相信你会喜欢这个的。
我们要造的轮子是一个最简单的栈的实现,用 C/C++来编写能够减小不必要的开销,带来显著的加速。
步骤
- 建立目录
- 编写 C++文件
- 编写 pyx 文件
- 直接编译
- 测试
1. 建立目录
首先,建立我们的工作目录。
mkdir pystackcd pystack
32 位版本和 64 位版本会带来不同的问题。我的 C 库是 32 位的,所以 python 库必须也是 32 位。
使用 pipenv 指定 python 版本,并安装 Cython。
pipenv --python P:\Py3.6.5\python.exepipenv 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;nt->prev = tail;nt->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* valval=self._c_stack.peek()if val==NULL:raise StackEmptyreturn <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* valval=self._c_stack.pop()if val==NULL:raise StackEmptycdef 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]11Traceback (most recent call last):File "test.py", line 13, in <module>print(st.pop())File "pystack.pyx", line 32, in pystack.Stack.popcpdef object pop(self):File "pystack.pyx", line 36, in pystack.Stack.popraise StackEmptypystack.StackEmpty
与正常 Python 对象表现相同,完美!
6. 应用
pipenv run python test_polish_notation.py- ``
from operator import add, sub, mul, truedivfrom fractions import Fractionfrom 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中文开发者的 精神部落
.jpg")
\
\
Python中文社区作为一个去中心化的全球技术社区,以成为全球20万Python中文开发者的精神部落为愿景,目前覆盖各大主流媒体和协作平台,与阿里、腾讯、百度、微软、亚马逊、开源中国、CSDN等业界知名公司和技术社区建立了广泛的联系,拥有来自十多个国家和地区数万名登记会员,会员来自以公安部、工信部、清华大学、北京大学、北京邮电大学、中国人民银行、中科院、中金、华为、BAT、谷歌、微软等为代表的政府机关、科研单位、金融机构以及海内外知名公司,全平台近20万开发者关注。
▼ 点击下方****阅读原文 , 免费成为****社区会员