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環境。
-
安裝cython,下面demo均在conda下使用。conda install cython
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