用 Cython 造個輪子
看什麼看!點我呀!
全棧程序員,免費入門到精通!
作者丨Nugine
https://zhuanlan.zhihu.com/c_168195059
在本篇文章中,我要向你展示使用 Cython 擴展 Python 的技巧。
如果你同時有 C/C++和 Python 的編碼能力,我相信你會喜歡這個的。
我們要造的輪子是一個最簡單的棧的實現,用 C/C++來編寫能夠減小不必要的開銷,帶來顯著的加速。
步驟
建立目錄
編寫 C++文件
編寫 pyx 文件
直接編譯
測試
1. 建立目錄
首先,建立我們的工作目錄。
cdmkdir 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"
"C" #include "c_stack.h"extern
}
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*
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
Nonecpdef
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,Py_INCREF,Py_DECREF。
從"c_stack.h"導入 C 符號: C_Stack,以及它的介面。
將其包裝為 Python 對象。
注意點:
在 C 實現中,當棧為空時,返回了空指針。Python 實現中檢查空指針,並拋出異常 StackEmpty.
PyObject* 和 object 並不等同,需要做類型轉換。
push 和 pop 時要正確操作引用計數,否則會讓 Python 解釋器直接崩潰。一開始不知道這個,懵逼好久,偶然間看到報錯與 gc 有關,才想到引用計數的問題。
4. 直接編譯
pipenv run cythonize -a -i pystack .pyx
生成三個文件: pystack.cpp,pystack.html,pystack.cp36-win32.pyd
pyx 編譯到 cpp,再由 C 編譯器編譯為 pyd。
html 是 cython 提示,指出 pyx 代碼中與 python 的交互程度。
pyd 就是最終的 Python 庫了。
5. 測試一下
"test.py"
from import print as print
st=Stack()
try:
st.pop()
except StackEmpty
for
iin
["1"
,1
,[1.0
],1
,dict(a=1
)]:st.push(i)
while
True: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"
, line13
,in
<module
>File
"pystack.pyx"
, line32
,in
pystack.Stack.popcpdef object pop(self):
File
"pystack.pyx"
, line36
,in
pystack.Stack.popraise StackEmpty
pystack.StackEmpty
與正常 Python 對象表現相同,完美!
6. 應用
pipenv run python test_polish_notation.py
from
operatorimport
add, sub, mul, truedivfrom
fractionsimport
Fractionfrom
pystackimport
Stackdef
main
()
:exp = input(
"exp: "
)val = eval_exp(exp)
print(
f"val:
{val}
")op_map = {
"+"
: add,"-"
: sub,"*"
: mul,"/"
: truediv}
def
convert
(exp)
:for
itin
reversed(exp.split(" "
)):if
itin
op_map:yield
True
, op_map[it]else
:yield
False
, Fraction(it)def
eval_exp
(exp)
:stack = Stack()
for
is_op, itin
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 造輪子技巧,希望能為即將進坑和已經進坑的同學提供一塊墊腳石。如果對你有所幫助,請點贊和收藏。
推薦↓↓↓
長
按
關
注
??
【
16個技術公眾號
】都在這裡!
涵蓋:程序員大咖、源碼共讀、程序員共讀、數據結構與演算法、黑客技術和網路安全、大數據科技、編程前端、Java、Python、Web編程開發、Android、iOS開發、Linux、資料庫研發、幽默程序員等。
覺得我們「好看」的點亮它~
TAG:Python開發 |