天天看點

cython簡單使用方法cython簡單使用方法

cython簡單使用方法

一、 介紹

python是一種高層級的,動态的,解釋性的,易學的語言,但是其帶來的副作用是,運作效率可能會比靜态編譯語言慢幾個數量級。我們可以使用python調用外部接口的方式,極大的提高python的運作效率,cython正是一種可以為Python編寫接口的語言。相當于Python做前端的計算,背景的運作就交給用c或者c++實作的這些動态庫來完成了,效率相比之前快了很多,既擁有了Python的便捷,又擁有了靜态語言的速度,實在是不亦樂乎!

cudf當然也注意到了這一點,是以就使用cython來為pandas寫封裝了,大大提升了資料分析的速度。那麼既然cython這麼神奇我們又怎麼使用cython為Python編寫接口呢?

二、三個小例子

請看範例:https://github.com/zhangjiaxinghust/cpp_warp_python_demo

環境配置:(ubuntu 16.04)

  • 下載下傳conda安裝腳本 https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh 運作安裝conda環境。
  • conda install cython

    安裝cython,下面demo均在conda下使用。

1. 簡單入門示例demo_simple

我們使用的類示例是一個矩形類,四個變量x0,y0,x1,y1代表矩形的變量位置,定義了構造函數,析構函數,提供了三個函數

int getArea();

void getSize(int* width, int* height);

void move(int dx, int dy);

分别在

Rectangle.h

Rectangle.cpp

中完成聲明和編寫。

同時我們可以看到有兩個檔案

Rectangle.pxd

rect.pyx

,他們的字尾名是不一樣的,這類似于c++中的

.h

檔案和

.cpp

檔案,

.pxd

檔案中有 Cython 子產品要包含的 Cython 聲明 (或代碼段)。

.pxd

檔案可共享外部 C 語言聲明,也能包含 C 編譯器内聯函數。可用

cimport

關鍵字将

.pxd

檔案導入

.pyx

子產品檔案中。

.pyx

檔案是由 Cython 程式設計語言 “編寫” 而成的 Python 擴充子產品源代碼檔案。

.pyx

檔案類似于 C++ 語言的 .cpp 源代碼檔案,

.pyx

檔案中有 Cython 子產品的源代碼。不像 Python 語言可直接解釋使用的

.py

檔案,

.pyx

檔案必須先被編譯成

.c

或者

.cpp

檔案,再編譯成

.pyd

(Windows 平台) 或

.so

(Linux 平台) 檔案,才可作為子產品 import 導入使用。

Rectangle.pxd

檔案内容如下:

# distutils: sources = Rectangle.cpp
cdef extern from "Rectangle.cpp":
    pass

# Declare the class with cdef
cdef extern from "Rectangle.h" namespace "shapes":
    cdef cppclass Rectangle:
        Rectangle() except +
        Rectangle(int, int, int, int) except +
        int x0, y0, x1, y1
        int getArea()
        void getSize(int* width, int* height)
        void move(int, int)
           

檔案開始的語句

cdef extern from "Rectangle.cpp"

不可或缺,它代表後面的

.h

檔案中定義的函數體(如果沒有編寫隻做了聲明)應該如何去尋找,聲明之後後續編譯程式會自動從

Rectangle.cpp

中去尋找對應的代碼。

下面的就是從.h中引用具體的函數,寫法和c++文法大同小異,值得注意的是兩個後面函數後面添加了

except +

語句。如果C ++代碼或初始記憶體配置設定由于故障而引發異常,這将使Cython安全地引發适當的Python異常。沒有此聲明,Cython将不會處理源自構造函數的C ++異常。

這樣我們就聲明了cython可以調用的c++接口,但是這個接口Python依然無法調用,我們需要對這個接口進行包裝之後Python才可以使用,具體包裝在檔案

rect.pyx

# distutils: language = c++

from Rectangle cimport Rectangle

cdef class PyRectangle:
    cdef Rectangle c_rect

    def __cinit__(self, int x0, int y0, int x1, int y1):
        self.c_rect = Rectangle(x0, y0, x1, y1)

    def get_area(self):
        return self.c_rect.getArea()

    def get_size(self):
        cdef int width, height
        self.c_rect.getSize(&width, &height)
        return width, height

    def move(self, dx, dy):
        self.c_rect.move(dx, dy)

    # Attribute access
    @property
    def x0(self):
        return self.c_rect.x0
    @x0.setter
    def x0(self, x0):
        self.c_rect.x0 = x0

    # Attribute access
    @property
    def x1(self):
        return self.c_rect.x1
    @x1.setter
    def x1(self, x1):
        self.c_rect.x1 = x1

    # Attribute access
    @property
    def y0(self):
        return self.c_rect.y0
    @y0.setter
    def y0(self, y0):
        self.c_rect.y0 = y0

    # Attribute access
    @property
    def y1(self):
        return self.c_rect.y1
    @y1.setter
    def y1(self, y1):
        self.c_rect.y1 = y1
           

首先

from Rectangle cimport Rectangle

表示我們從上面編寫的檔案中引入

Rectangle

類,然後我們就可以使用類定義的接口了。我們執行個體化了一個對象,然後聲明了各個函數和變量的

set

get

方法。值得注意的是Cython使用空構造函數初始化cdef類的C ++類屬性。如果要包裝的類沒有null構造函數,則必須存儲指向包裝好的類的指針,然後手動配置設定和取消配置設定它。這樣做的友善和安全的方式是

__cinit__

__dealloc__

方法,這些方法保證在建立和删除Python執行個體時精确地調用一次。

聲明好Python可以使用的接口之後下面我們就可以在Linux下面生成Python可以調用的動态庫了。這時候

setup.py

就要閃亮登場了。

from distutils.core import setup
from Cython.Build import cythonize

setup(
    ext_modules = cythonize(
    "rect.pyx",
    language="c++", ## 如果是C++就需要
))
           

我們在檔案中指明需要編譯的檔案是

rect.pyx

,定義好語言是

c++

,然後運作

python setup.py build_ext --inplace

就大功告成了。之前說過cython是先将cython檔案翻譯為

.c

檔案或

.cpp

檔案然後編譯為Python可以調用的動态庫,是以你會發現目錄下會多了一個

rect.cpp

檔案,這個檔案就是中間的翻譯檔案。生成

.so

檔案之後,我們進行測試,測試檔案如下:

import rect

pyRect = rect.PyRectangle(100, 100, 300, 500)
width, height = pyRect.get_size()
print("size: width = %d, height = %d" % (width, height))
           

Python運作之後輸出正确就代表大功告成了!

2. 進階靜态編譯入門示例demo_static

很多情況下項目很大,我們根本無法像上面那樣在一個目錄下完成,那又該怎麼去編寫呢?

可以告訴大家的是,cython非常的人性化,大家現在可以把

setup.py

看做是一個有着類似

gcc

功能的編譯配置檔案。如果更改了目錄結構之後不改變

setup.py

的任何内容的話,直接編譯會報錯,因為這時候在連結翻譯檔案

rect.cpp

的時候找不到源碼檔案。這時後我們隻需要将

setup.py

做小小的修改即可。

from distutils.core import setup
from Cython.Build import cythonize
from setuptools.extension import Extension


cython_files = ["_lib/*.pyx"]

extensions = [
    Extension(
        name="rect",
        sources=cython_files,
        include_dirs=[
            "../c++/_code",
            "../c++/_lib",
            "../../c++",
        ],
        language="c++",
    )
]

setup(
    ext_modules=cythonize(extensions)
)
           

name

代表我們編譯出的動态庫的名稱。

include_dirs

代表傳給 gcc 的 -I 參數` ,隻需要稍作指定即可,是不是感覺so easy!

3. 進階動态編譯入門示例demo_dynamic

很多時候,随着我們項目的開發,靜态編譯有很多缺點,那麼我們是否可以進行動态編譯呢?

可以發現,cython本身就是先翻譯為

.c

或者

cpp

檔案然後再進行連結,自然可以支援動态編譯了。隻需要在連結的時候連結對應的動态庫,然後執行是尋找動态庫就可以了。同樣使用動态連結的話我們需要在上面靜态例子中更改

setup.py

Rectangle.pxd

我們需要在

Rectangle.pxd

中去掉

cdef extern from "Rectangle.cpp"

,因為我們是動态連結,直接尋找動态庫中的函數聲明就可以,相應的我們需要在

setup.py

中添加關于動态庫的聲明:

from distutils.core import setup
from Cython.Build import cythonize
from setuptools.extension import Extension


cython_files = ["_lib/*.pyx"]

extensions = [
    Extension(
        name="rect",
        sources=cython_files,
        include_dirs=[
            "../c++/_lib",
        ],
        library_dirs=["../"],
        libraries=["Rectangle"],
        language="c++",
    )
]

setup(
    ext_modules=cythonize(extensions)
)
           

library_dirs

這個就是傳給 gcc 的 -L 參數,我們指定對應的動态連結庫的目錄。

libraries

這個就是傳給 gcc 的 -l 參數,我們指定動态連結庫的名稱。同時,我們隻需要在

setup.py

中指定

.h

頭檔案所在的目錄即可。

官方參考:https://cython.readthedocs.io/en/latest/src/userguide/wrapping_CPlusPlus.html