cython
Cython是一个编程语言,它通过类似Python的语法来编写C扩展并可以被Python调用.既具备了Python快速开发的特点,又可以让代码运行起来像C一样快,同时还可以方便地调用C library。
1. 环境配置
1.1 windows
安装MingW-w64编译器:conda install libpython m2w64-toolchain -c msys2
在Python安装路径下找到\Lib\distutils文件夹,创建distutils.cfg写入如下内容:
[build] compiler=mingw32
1.2 macOS
安装XCode
1.3 linux
sudo apt-get install build-essential
1.4 安装cython
- pip install cython
- conda install cython
2. 例子:矩阵乘法
2.1 python
# dot_python.py
import numpy as np
def naive_dot(a, b):
if a.shape[1] != b.shape[0]:
raise ValueError('shape not matched')
n, p, m = a.shape[0], a.shape[1], b.shape[1]
c = np.zeros((n, m), dtype=np.float32)
for i in xrange(n):
for j in xrange(m):
s = 0
for k in xrange(p):
s += a[i, k] * b[k, j]
c[i, j] = s
return c
2.2 cython
# dot_cython.pyx
import numpy as np
cimport numpy as np
cimport cython
@cython.boundscheck(False)
@cython.wraparound(False)
cdef np.ndarray[np.float32_t, ndim=2] _naive_dot(np.ndarray[np.float32_t, ndim=2] a, np.ndarray[np.float32_t, ndim=2] b):
cdef np.ndarray[np.float32_t, ndim=2] c
cdef int n, p, m
cdef np.float32_t s
if a.shape[1] != b.shape[0]:
raise ValueError('shape not matched')
n, p, m = a.shape[0], a.shape[1], b.shape[1]
c = np.zeros((n, m), dtype=np.float32)
for i in xrange(n):
for j in xrange(m):
s = 0
for k in xrange(p):
s += a[i, k] * b[k, j]
c[i, j] = s
return c
def naive_dot(a, b):
return _naive_dot(a, b)
2.3 差异点
-
Cython 程序的扩展名是 .pyx
-
cimport 是 Cython 中用来引入 .pxd 文件的命令,可以简单理解成 C/C++ 中用来写声明的头文件
-
@cython.boundscheck(False) 和 @cython.wraparound(False) 两个修饰符用来关闭 Cython 的边界检查
-
Cython 的函数使用 cdef 定义,并且他可以给所有参数以及返回值指定类型。比方说,我们可以这么编写整数 min 函数:
cdef int my_min(int x, int y): return x if x <= y else y
-
在函数体内部,我们一样可以使用 cdef typename varname 这样的语法来声明变量
-
在 Python 程序中,是看不到 cdef 的函数的,所以我们这里 def naive_dot(a, b) 来调用 cdef 过的 _naive_dot 函数
2.4 Cython 编译后被 Python 调用
- Cython 编译器把 Cython 代码编译成调用了 Python 源码的 C/C++ 代码
- 把生成的代码编译成动态链接库
- Python 解释器载入动态链接库
前两步
写代码
# setup.py
from distutils.core import setup, Extension
from Cython.Build import cythonize
import numpy
setup(ext_modules = cythonize(Extension(
'dot_cython',
sources=['dot_cython.pyx'],
language='c',
include_dirs=[numpy.get_include()],
library_dirs=[],
libraries=[],
extra_compile_args=[],
extra_link_args=[]
)))
- 'dot_cython' 是我们要生成的动态链接库的名字
- sources 里面可以包含 .pyx 文件,以及后面如果我们要调用 C/C++ 程序的话,还可以往里面加 .c / .cpp 文件
- language 其实默认就是 c,如果要用 C++,就改成 c++ 就好了
- include_dirs 这个就是传给 gcc 的 -I 参数
- library_dirs 这个就是传给 gcc 的 -L 参数
- libraries 这个就是传给 gcc 的 -l 参数
- extra_compile_args 就是传给 gcc 的额外的编译参数,比方说你可以传一个 -std=c++11
- extra_link_args 就是传给 gcc 的额外的链接参数(也就是生成动态链接库的时候用的)
- 如果你从来没见过上面几个 gcc 参数,说明你暂时还没这些需求,等你遇到了你就懂了
然后我们只需要执行下面命令就可以把 Cython 程序编译成动态链接库了。
python setup.py build_ext --inplace
成功运行完上面这句话,可以看到在当前目录多出来了 dot_cython.c 和 dot_cython.so。前者是生成的 C 程序,后者是编译好了的动态链接库。
3. 例子:求质数
prime.pyx
# distutils: language=c++
from libcpp.vector cimport vector
def prime_py(number):
plist = []
for n in range(2, number + 1):
for x in range(2, n):
if n % x == 0:
break
else:
plist.append(n)
return plist
def prime_cy(int number):
cdef int x, n
cdef vector[int] plist
for n in range(2, number + 1):
for x in range(2, n):
if n % x == 0:
break
else:
plist.push_back(n)
return plist
setup.py
from setuptools import setup
from Cython.Build import cythonize
setup(
ext_modules=cythonize("prime.pyx")
)
准备
python setup.py build_ext --inplace
test_prime.py
import prime
import time
cy_start = time.time()
prime.prime_cy(50000)
cy_end = time.time()
print('cython : ',cy_end - cy_start)
py_start = time.time()
prime.prime_py(50000)
py_end = time.time()
print('python : ',py_end - py_start)
4. 例子: 从 c++ 里导入函数
demo.h
#ifndef DEMO_H
#define DEMO_H
using namespace std;
namespace demo {
class MyDemo {
public:
int a;
MyDemo();
MyDemo(int a );
~MyDemo();
int mul(int m );
int add(int b);
void sayHello(char* name);
};
}
int func(int x){
return x * 10;
}
#endif
demo.cpp
#include "demo.h"
#include <iostream>
namespace demo {
MyDemo::MyDemo () {}
MyDemo::MyDemo (int a) {
this->a = a;
}
MyDemo::~MyDemo () {}
int MyDemo::mul(int m) {
return this->a*m;
}
int MyDemo::add (int b) {
return this->a+b;
}
void MyDemo::sayHello(char* name){
cout<<"hello "<<name<<"!"<<endl;
}
}
cdemo.pyd
- pyd连接c++和pyx
cdef extern from "demo.cpp":
pass
# Decalre the class with cdef
cdef extern from "demo.h" namespace "demo":
cdef cppclass MyDemo:
MyDemo() except +
MyDemo(int) except +
int a
int mul(int )
int add(int )
void sayHello(char*)
cdef extern from "demo.h":
int func(int )
cpyep.pyx
- pyx 连接 pyd 和 python
# distutils: language = c++
from cdemo cimport MyDemo
# Create a Cython extension type which holds a C++ instance
# as an attribute and create a bunch of forwarding methods
# Python extension type.
cdef class PyMyDemo:
cdef MyDemo c_mydemo # Hold a C++ instance which we're wrapping
def __cinit__(self,a):
self.c_mydemo = MyDemo(a)
def mul(self, m):
return self.c_mydemo.mul(m)
def add(self,b):
return self.c_mydemo.add(b)
def sayHello(self,name ):
self.c_mydemo.sayHello(name)
from cdemo cimport func
def funcc(int x):
return func(x)
setup.py
- 生成动态库
python setup.py build_ext --inplace
from setuptools import setup
from Cython.Build import cythonize
setup(ext_modules=cythonize("cpyep.pyx"))
test.py
from cpyep import *
print(funcc(10))
# output : 100
一些注意点
- 函数签名基本上可以原样从 C/C++ 复制到 Cython 中
- C 中的 _Bool 类型和 C++ 中的 bool 类型在 Cython 中都用 bint 取代(因为 Python 没有布尔类型)
- struct / enum / union 是支持的
- const 限定和引用都是支持的
- 命名空间是支持的
- C++ 类是支持的
- 部分操作符重载是支持的,部分操作符需要改名
- 内嵌类是支持的
- 模板是支持的
- 异常是支持的
- 构造函数、析构函数是支持的
- 静态成员是支持的
- libc / libcpp / STL 是支持的
- 声明写在 .pxd 中可以在 .pyx 中 cimport 进来