文章目录 一. Cython简介 一. Cython编译 2.1. 编译过程 2.2. 环境安装 2.3. disutils库 2.4. 引入C源文件 三. 总结 参考文献
一. Cython简介
Cython官网地址:https://cython.org/ Cython的下载和安装:https://pypi.org/project/Cython/
Cython是一个快速生成Python扩展模块的工具,从语法层面上来讲是 Python语法和C语言语法的混血 ,当Python性能遇到瓶颈时,Cython直接将C的原生速度植入Python程序,这样使Python程序无需使用C重写,能快速整合原有的Python程序,这样使得开发效率和执行效率都有很大的提高,而这些中间的部分,都是Cython帮我们做了。
一. Cython编译
因为Cython是 Python 的超集,所以 Python 解释器无法直接运行 Cython 的代码,那么如何才能将 Cython 代码变成 Python 解释器可以识别的有效代码呢?答案是通过 Cython 编译 Pipeline。 Pipeline 的职责就是将 Cython 代码转换成 Python 解释器可以直接导入并使用的 Python 扩展模块 ,这个 Pipeline 可以在不受用户干预的情况下自动运行(使 Cython 感觉像 Python 一样),也可以在需要更多控制时由用户显式的运行。
2.1. 编译过程
Pipeline由两步组成 :第一步 是由 cython 编译器负责将 Cython 转换成经过优化并且依赖当前平台的 C、C++ 代码;第二步 是使用标准的 C、C++ 编译器将第一步得到的 C、C++ 代码进行编译并生成标准的扩展模块,并且这个扩展模块是依赖特定的平台的。如果是在 Linux 或者 Mac OS,那么得到的扩展模块的后缀名为 .so ,如果是在 Windows 平台,那么得到的扩展模块的后缀名为 .pyd(扩展模块 .pyd 本质上是一个 DLL 文件) 。不管是什么平台,最终得到的都会是一个成熟的 Python 扩展模块,它是可以直接被 Python 解释器进行 import 的。
注意: Cython编译器是一种源到源的编译器,并且生成的扩展模块也是经过高度优化的,因此Cython生成的C代码编译得到的扩展模块比手写的C代码编译得到的扩展模块运行的要快并不是一件稀奇的事情。因为 Cython 生成的 C 代码是经过高度精炼,所以大部分情况下比手写所使用的算法更优 ,而且 Cython 生成的 C 代码支持所有的通用 C 编译器,生成的扩展模块同时支持许多不同的 Python 版本。
2.2. 环境安装
现在我们知道在编译 Pipeline 中有两个步骤,而实现这两个步骤需要我们确保机器上有 C、C++ 编译器以及 Cython 编译器 ,不同的平台有不同的选择。
C、C++编译器: Linux 和 Mac OS无需多说,因为它们都自带 gcc ,但是注意:如果是 Linux 的话,我们还需要 yum install python3-devel(以 CentOS 为例)
。至于 Windows,可以下载一个 Visual Studio,但是那个玩意会比较大,如果不想下载 vs 的话,那么可以选择安装一个 MinGW 并设置到环境变量中,至于下载方式可以去https://sourceforge.net/projects/mingw/files/ 进行下载。
安装cython编译器 安装 cython 编译器的话,可以直接通过 pip install cython
即可。因此我们看到 cython 编译器只是 Python 的一个第三方包,因此运行 Cython 代码同样要借助 Python 解释器。
# 方式1
(allennlp_zkf) zkf@ubuntu:/data/aibox/kaifang/trans/cython$ cython -V
Cython version 0.29.32# 方式2
import Cython
print(Cython.__version__)
# 0.29.32
2.3. disutils库
Python有一个标准库disutils
,可以用来构建、打包、分发 Python 工程 。而其中一个对我们有用的特性就是 它可以借助C编译器将C源码编译成扩展模块,并且这个模块是自带的、考虑了平台、架构、Python 版本等因素 ,因此我们在任意地方使用disutils都可以得到扩展模块。注意:上面 disutils 只是帮我们完成了 Pipeline 的第二步,那第一步呢?第一步则是需要 cython 来完成。
斐波那契数列 ,指的是这样一个数列:1、1、2、3、5、8、13、21、34、55、89
def fib(n):""" 这是一个扩展模块 """cdef int icdef double a = 0.0, b = 1.0for i in range(n):a, b = a + b, areturn a # 最后一个数字
from distutils.core import setup
from Cython.Build import cythonize'''
我们说构建扩展模块的过程分为两步
1. 将Cython代码翻译成C代码;
2. 根据C代码生成扩展模块.
第一步要由cython编译器完成, 通过cythonize;
第二步要由distutils完成, 通过distutils.core下的setup'''
# 里面的 language_level=3 表示只需要兼容python3即可, 而默认是2和3都兼容
# 强烈建议加上这个参数, 因为目前为止我们只需要考虑python3即可
setup(ext_modules=cythonize("fib.pyx", language_level=3))'''
cythonize负责将Cython代码转成C代码, 这里我们可以传入单个文件, 也可以是多个文件组成的列表
或者一个glob模式, 会匹配满足模式的所有Cython文件; 然后setup根据C代码生成扩展模块
'''
编译后产生的文件: 这个文件叫做 setup.py,这里只是做了准备,但是还没有进行编译。我们需要终端执行 python setup.py build
进行编译。在我们执行命令之后,当前目录会多出一个build目录,里面的结构如下。重点是那个fib.cpython-37m-x86_64-linux-gnu.so(windows系统的话就是.pyd结尾的)
文件,该文件就是根据 fib.pyx
生成的扩展模块,至于其它的可以直接删掉了。我们把这个文件单独拿出来测试一下:
import fib
import tracebackprint(fib) # # try:
# # 我们在里面定义了一个fib函数, 在fib.so里面定义的函数在编译成扩展模块之后可以直接使用
# print(fib.fib("xx")) # 6765.0
#
#
# except Exception:
# print(traceback.format_exc())# 因为我们定义的是fib(int n), 而传入的不是整型, 所以直接报错
print(fib.fib(20)) # 6765.0# 我们的注释
print(fib.fib.__doc__)
# 这是一个扩展模块
Traceback (most recent call last):File "/data/aibox/kaifang/trans/cython/test_fib.py", line 16, in print(fib.fib("xx")) # 6765.0File "fib.pyx", line 14, in fib.fibfor i in range(n):
TypeError: an integer is required55.0这是一个扩展模块
2.4. 引入C源文件
除此之外我们还可以嵌入 C、C++ 的代码 ,我们来看一下。
// cfib.h
double cfib(int n); // 定义一个函数声明//cfib.c
double cfib(int n) {int i;double a=0.0, b=1.0, tmp;for (i=0; itmp = a; a = a + b; b = tmp;}return a;
} // 函数体的实现
# 通过 cdef extern from 导入头文件, 写上里面的函数
cdef extern from "cfib.h":double cfib(int n)# 然后 Cython 可以直接调用
def fib_with_c(n):"""调用 C 编写的斐波那契数列"""return cfib(n)
from distutils.core import setup, Extension
from Cython.Build import cythonize# 我们看到之前是直接往 cythonize 里面传入一个文件名即可
# 但是现在我们传入了一个 Extension 对象, 通过 Extension 对象的方式可以实现更多功能
# 这里指定的 name 表示编译之后的文件名, 显然编译之后会得到 wrapper_cfib.cp38-win_amd64.pyd
# 如果是之前的方式, 那么得到的就是 fib.cp38-win_amd64.pyd, 默认会和 .pyx 文件名保持一致, 这里我们可以自己指定
# sources 则是代表源文件, 这里我们只需要指定 pyx 和 c 源文件即可, 因为头文件也在同一个目录中
# 如果不在, 那么还需要通过 include_dirs 指定头文件的所在目录, 不然 extern from "cfib.h" 就报错了
ext = Extension(name="wrapper_cfib", sources=["fib.pyx", "cfib.c"])
setup(ext_modules=cythonize(ext))
# !/usr/bin/env python
# -*- encoding: utf-8 -*-
import wrapper_cfibprint(wrapper_cfib.fib_with_c(20)) # 6765.0
print(wrapper_cfib.fib_with_c.__doc__) # 调用 C 编写的斐波那契数列
6765.0
调用 C 编写的斐波那契数列Process finished with exit code 0
我们看到成功调用 C 编写的斐波那契数列,这里我们使用了一种新的创建扩展模块的方法,我们来总结一下。 如果是单个pyx文件的话, 那么直接通过 cythonize("xxx.pyx")
即可; 如果 pyx 文件还引入了 C 文件, 那么通过 cythonize(Extension(name="xx", sources=["", ""]))
的方式即可;name 是编译之后的扩展模块的名字, sources 是你要编译的源文件, 我们这里是一个 pyx 文件一个 C 文件;
建议后续都使用第二种方式,可定制性更强,而且我们之前使用的 cythonize("fib.pyx")
完全可以用 cythonize(Extension("fib", ["fib.pyx"]))
进行替代。
三. 总结
目前我们介绍了如何将 pyx 文件编译成扩展模块,对于一个简单的 pyx 文件来说,方法如下:
from distutils.core import setup, Extension
from Cython.Build import cythonize# 推荐以后就使用这种方法
ext = Extension(name="wrapper_fib", # 生成的扩展模块的名字sources=["fib.pyx"], # 源文件
)
setup(ext_modules=cythonize(ext, language_level=3)) # 指定Python3
参考文献
Cython官网地址:https://cython.org/ 《Cython系列》2.编译并运行 Cython 代码的几种方式:https://www.cnblogs.com/traditional/p/13213173.html https://www.cnblogs.com/traditional/ https://www.jianshu.com/p/9053dacee822