當前位置:
首頁 > 知識 > 用 Cython 造個輪子

用 Cython 造個輪子


看什麼看!點我呀!

全棧程序員,免費入門到精通!



作者丨Nugine



https://zhuanlan.zhihu.com/c_168195059

在本篇文章中,我要向你展示使用 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;
    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

 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,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

 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 造輪子技巧,希望能為即將進坑和已經進坑的同學提供一塊墊腳石。如果對你有所幫助,請點贊和收藏。

 推薦↓↓↓ 






??

16個技術公眾號

】都在這裡!


涵蓋:程序員大咖、源碼共讀、程序員共讀、數據結構與演算法、黑客技術和網路安全、大數據科技、編程前端、Java、Python、Web編程開發、Android、iOS開發、Linux、資料庫研發、幽默程序員等。

覺得我們「好看」的點亮它~

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 Python開發 的精彩文章:

前人挖坑,後人填坑
python3 拼接字元串的7種方法

TAG:Python開發 |