Cython
简介
https://github.com/cython/cython
官网
官方文档
中文文档
Python 有调用 C 语言代码的能力,这意味着,可以用 C 语言重写某些代码来提升程序性能
Cython 是 Python 的超集。它几乎是 Python 和 C 语言的合并,是一种渐进类型的语言。
任何 Python 代码都是有效 Cython 代码。
Cython 代码可以编译成 C 语言代码,使用 Cython 可以编写一个模块或者一个方法,并逐渐扩展到越来越多的 C 语言类型,还可以将 C 语言类型和 Python 类型混合在一起,使用 Cython 可以获得混合后的完美组合,只在瓶颈处使用 C 语言优化,而在其他地方仍然可以享有 Python 的便捷与优雅
当 Python 的性能问题产生阻碍时,不需要把整个代码库用另一种语言来编写,只需要用 Cython 重写几个函数几乎就能得到所需要的性能,或者使用其他方法优化 Python
cython 的 vscode 插件: Language-Cython
第一个例子
| python setup.py --help-commands
python setup.py build_ext --help
|
由于 Cython 能接受几乎所有的合法 Python 源文件,开始使用 Cython 的最难的事情之一是怎么编译你的拓展(extension)。
从一个简单的例子开始:
新建一个hello
的文件夹
在文件夹下面新建一个hello.pyx
,内容如下
| def say_hello():
print("Hello!!!")
|
再新建一个文件setup.py
:
| from distutils.core import setup
from Cython.Build import cythonize
setup(
ext_modules = cythonize("hello.pyx")
)
|
执行如下命令构建:
| python setup.py build_ext --inplace
|
运行完之后会生成hello.cpython-37m-x86_64-linux-gnu.so
和hello.c
在当前文件夹下面打开 ipython:
| In [1]: import hello
In [2]: hello.say_hello()
Hello!!!
In [3]: hello
Out[3]: <module 'hello' from '/home/zhaoyangzhen/hello/hello.cpython-37m-x86_64-linux-gnu.so'>
In [4]: ls
build/ hello.cpython-37m-x86_64-linux-gnu.so* setup.py hello.c hello.pyx
|
如果在其他目录import hello
是无法调用hello.say_hello()
的
如果删除掉build
目录和hello.c
,在当前目录也是可以正常使用这个模块的
执行python setup.py install
这个命令,就可以在其他地方使用hello
这个模块了
| In [2]: import hello
In [3]: hello
Out[3]: <module 'hello' from '/home/zhaoyangzhen/anaconda3/lib/python3.7/site-packages/hello.cpython-37m-x86_64-linux-gnu.so'>
In [4]: hello.say_hello()
Hello!!!
|
调用 C/C++ 函数
https://github.com/cython/cython/tree/master/Cython/Includes
从 Cpython 导入
导入 C 函数
先以一个 C 标准库中的函数为例。 你不需要向你的代码中引入额外的依赖,Cython 都已经帮你定义好了这些函数。所以你可以将这些函数直接 cimport 进来并使用。
举个例子,比如说当你想用最简单的方法将char*
类型的值转化为一个整型值时, 你可以使用atoi()
函数,这个函数是在stdlib.h
头文件中定义的。我们可以这样来写:
hello.pyx
:
| from libc.stdlib cimport atoi
cdef parse_charptr_to_py_int(char* s):
assert s is not NULL, "byte string value is NULL"
return atoi(s) # note: atoi() has no error detection!
def test(s):
return parse_charptr_to_py_int(s)
|
| python setup.py build
python setup.py install
|
| In [1]: import hello
In [2]: hello.test('23333.33'.encode())
Out[2]: 23333
|
导入 C++ 函数
setup.py
:
| from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
extensions = [
Extension("demo", sources=["demo.pyx"], extra_compile_args=["-O2"], language="c++")
]
setup(
ext_modules = cythonize(extensions)
)
|
demo.pyx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | from libcpp.vector cimport vector
def primes(unsigned int nb_primes):
cdef int n, i
cdef vector[int] p
p.reserve(nb_primes) # allocate memory for 'nb_primes' elements.
n = 2
while p.size() < nb_primes: # size() for vectors is similar to len()
for i in p:
if n % i == 0:
break
else:
p.push_back(n) # push_back is similar to append()
n += 1
# Vectors are automatically converted to Python
# lists when converted to Python objects.
return p
|
| python setup.py build
python setup.py install
|
| In [1]: import demo
In [2]: demo.primes(5)
Out[2]: [2, 3, 5, 7, 11]
In [3]: demo.primes(10)
Out[3]: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
|
外部引入
demo.pyx
| cdef extern from "math.h":
double sin(double x)
def py_sin(x):
return sin(x)
|
| In [1]: import demo
In [2]: demo.py_sin(0)
Out[2]: 0.0
In [4]: demo.py_sin(3.14 / 2)
Out[4]: 0.9999996829318346
|
demo.pyx
| cdef extern from "stdio.h":
extern int printf(const char *format, ...)
def SayHello():
printf("hello,world\n")
|
| In [1]: import demo
In [2]: demo.SayHello()
hello,world
|
test.cpp
| #include <cstdio>
#include <map>
#include <algorithm>
using namespace std;
int test() {
map<int, int> m;
m[1] = 2333; m[2] = 666;
int x = max(m[1], m[2]);
return x;
}
|
test.h
| #ifndef _TEST_H
#define _TEST_H
int test();
#endif
|
setup.py
| from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
extensions = [
Extension("demo", sources=["demo.pyx", "test.cpp"], extra_compile_args=["-O2"], language="c++"),
]
setup(
ext_modules = cythonize(extensions)
)
|
demo.pyx
| cdef extern from "test.h":
int test()
def py_test():
print(test())
|
| In [1]: import demo
In [2]: demo.py_test()
2333
|
变量的命名
C 和 Cython 都支持没有参数名的函数声明
| cdef extern from "string.h":
char* strstr(const char*, const char*)
|
然而,这样的话 Cython 代码将不能通过关键字参数来调用这个函数,所以,我们最好这样去声明一个函数:
| cdef extern from "string.h":
char* strstr(const char* src_str, const char* sub_str)
|
这会让清楚地知道你所调用了哪两个参数,从而能够避免二义性并增强你的代码的可读性:
demo.pyx
| cdef extern from "string.h":
char* strstr(const char* src_str, const char* sub_str)
def py_strstr():
cdef char* data = "hfvcakdfagbcffvschvxcdfgccbcfhvgcsnfxjh"
pos = strstr(sub_str='akd', src_str=data)
print(pos != NULL)
|
| In [1]: import demo
In [2]: demo.py_strstr()
True
|
将 Python list 传递给 C/C++ 函数
setup.py
| import numpy
from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
extensions = [
Extension("demo", sources=["demo.pyx", "test.cpp"], extra_compile_args=["-O2"], language="c++"),
]
setup(
ext_modules = cythonize(extensions)
)
|
test.h
| #ifndef _TEST_H
#define _TEST_H
#include <vector>
#include <list>
int test(std::list<int> &);
#endif
|
test.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | #include <cstdio>
#include <map>
#include <vector>
#include <algorithm>
#include "test.h"
using namespace std;
int test(list<int> &l) {
for(list<int>::const_iterator i = l.begin(); i != l.end(); i++) {
printf("%d\n", *i);
}
map<int, int> m;
m[1] = 2333; m[2] = 666;
int x = max(m[1], m[2]);
return 0;
}
|
demo.pyx
| from libcpp.list cimport list
cdef extern from "test.h":
int test(list[int] &)
def open_price(list[int] l):
test(l)
|
| In [1]: import demo
In [3]: demo.open_price([1, 2, 3, 5])
1
2
3
5
|
将list
改成vector
也是一样的效果
使用 C int 指针
test.h
| #ifndef _TEST_H
#define _TEST_H
#include <vector>
#include <list>
int test(int* a);
#endif
|
test.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | #include <cstdio>
#include <map>
#include <vector>
#include <algorithm>
#include "test.h"
using namespace std;
int test(int* a) {
int ret = a[0];
map<int, int> m;
m[1] = 2333; m[2] = 666;
int x = max(m[1], m[2]);
return ret;
}
|
demo.pyx
| from libcpp.list cimport list
cdef extern from "test.h":
int test(int *a)
def open_price():
cdef int *a = [2223333, 6, 5]
print(test(a))
|
| In [1]: import demo
In [2]: demo.open_price()
2223333
|
向 C/C++ 传递二维 list
test.h
| #ifndef _TEST_H
#define _TEST_H
#include <vector>
#include <list>
int test(int* a, int n, int m);
#endif
|
test.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | #include <cstdio>
#include <map>
#include <vector>
#include <algorithm>
#include "test.h"
using namespace std;
int test(int* a, int n, int m) {
printf("n = %d, m = %d\n", n, m);
int sum = 0;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
printf("%d ", a[i * m + j]);
sum += a[i * m + j];
}
}
printf("\n");
int ret = a[0];
return sum;
}
|
demo.pyx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | from libcpp.list cimport list
from libc.stdlib cimport malloc, free
cdef extern from "test.h":
int test(int *a, int n, int m)
def open_price(msgs):
cdef int *a = <int *>malloc(len(msgs[0]) * len(msgs) * sizeof(int))
n = len(msgs)
m = len(msgs[0])
print(n, m)
for i in range(n):
for j in range(m):
a[i * m + j] = msgs[i][j]
for i in range(n * m):
print(a[i])
print(test(a, n, m))
free(a)
|
1
2
3
4
5
6
7
8
9
10
11
12
13 | In [1]: import demo
In [2]: demo.open_price([[1, 2], [3, 4], [5, 6]])
(3, 2)
1
2
3
4
5
6
n = 3, m = 2
1 2 3 4 5 6
21
|
也可以直接使用二维vector
test.h
| #ifndef _TEST_H
#define _TEST_H
#include <vector>
#include <list>
using namespace std;
int test(vector<vector<int> > &);
#endif
|
test.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | #include <cstdio>
#include <map>
#include <vector>
#include <algorithm>
#include "test.h"
using namespace std;
int test(vector<vector<int> > &l) {
for (int i = 0; i < l.size(); ++i) {
for (int j = 0; j < l[i].size(); ++j) {
printf("%d\n", l[i][j]);
}
}
map<int, int> m;
m[1] = 2333; m[2] = 666;
int x = max(m[1], m[2]);
return 0;
}
|
demo.pyx
| from libcpp.vector cimport vector
cdef extern from "test.h":
int test(vector[vector[int]] &)
def open_price(l):
test(l)
|
| In [1]: import demo
In [2]: demo.open_price([[1, 2], [3, 4], [5, 7]])
1
2
3
4
5
7
|
使用结构体
test.h
1
2
3
4
5
6
7
8
9
10
11
12 | #ifndef _TEST_H
#define _TEST_H
#include <vector>
#include <list>
struct Node {
int a, b;
};
Node test();
#endif
|
test.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13 | #include <cstdio>
#include <map>
#include <vector>
#include <algorithm>
#include "test.h"
using namespace std;
Node test() {
Node node;
node.a = 1116;
node.b = 1917;
return node;
}
|
demo.pyx
| from libcpp.list cimport list
from libc.stdlib cimport malloc, free
cdef extern from "test.h":
cdef struct Node:
int a, b
Node test()
def open_price(msgs):
print(test())
|
| In [1]: import demo
In [2]: demo.open_price(0)
{'a': 1116, 'b': 1917}
|
使用 C++ STL 中的 map
test.h
| #ifndef _TEST_H
#define _TEST_H
#include <vector>
#include <list>
#include <map>
using namespace std;
map<int, int> test();
#endif
|
test.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | #include <cstdio>
#include <map>
#include <vector>
#include <algorithm>
#include "test.h"
using namespace std;
map<int, int> test() {
map<int, int> m;
m[1] = 2;
m[000001] = 930;
m[300306] = 23333;
return m;
}
|
demo.pyx
| from libcpp.map cimport map
cdef extern from "test.h":
map[int, int] test()
def open_price():
ans = test()
print(ans)
|
| In [1]: import demo
In [2]: demo.open_price()
{1: 930, 300306: 23333}
|
注意
执行 python setup.py install
时会根据 .pyx
文件的文件名去生成对应的 .c
或 .cpp
文件,所以之前不要有相同文件名的 .c
或 .cpp
文件
调试
当出现错误时,扩展可能会无法运行,这非常糟糕。
静态类型提供了许多优于 Python 的优点,可以让你在编译步骤中捕获很多问题。
而在 Python 中,如果没有严格的测试例程和全面的测试覆盖的情况下,就很难发现这些问题
另一方面,所有的内存管理必须手动操作。并且,错误的内存管理是 C 语言中大多数编程错误的主要原因。
在最好的情况下,这样的错误只会导致一些内存泄漏,这将逐渐消耗掉你所有的环境资源。
最好的情况并不意味着容易处理。
如果没有使用适当的外部工具,如 Valgrind,内存泄漏是非常棘手的。
总之,在大多数情况下,扩展代码中的内存管理问题将在 Python 中导致无法恢复的段错误,并且会导致解释器崩溃,并且不会抛出任何异常。
这意味着你最终需要使用大多数 Python 程序员不需要使用的额外的工具。这增加了开发环境和工作流程的复杂性