cython简单入门

发布时间 2023-11-29 16:52:51作者: InsiApple

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 调用

  1. Cython 编译器把 Cython 代码编译成调用了 Python 源码的 C/C++ 代码
  2. 把生成的代码编译成动态链接库
  3. 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 进来