Cython让你的Python代码性能起飞
Cython是个什么东西
Cython 是一种超集(superset)的 Python 语言,它允许你编写 Python 代码并将其编译为高效的 C 代码,以提升性能。
它的核心目标是加速 Python 代码,同时支持与 C 语言的无缝集成。
Cython 主要用于:
- 优化 Python 代码性能(比纯 Python 运行得更快)
- 调用 C/C++ 代码(作为 Python 和 C 之间的桥梁)
- 创建 Python 扩展模块(.pyd 或 .so 文件,可用于 Python 调用)
Cython通常我们将他读作”赛森”,前面的读音是cy,后面就和python的thon一样了。
需要注意的是Cython并不是Python的标准库,所以是需要如下命令进行安装的:pip install Cython
Cython所基于Python/C API的,但是初学Cython的时候可以完全不用了解Python/C API
使用
很重要的一点:
当在使用Cython将Python变成C/C++代码的时候,需要将代码中的部分类型声明为Cython特定类型注释,用于告诉Cython如何转换。
我们以求Fibonacci数列(斐波那契数列)为例,来看一下Cython的强大。
首先,你要会Python,用纯Python编写求解斐波那契数列的函数代码:
1 | def fib(n): |
pyx文件创建
接下来就开始我们Cython的骚操作了。我们需要创建一个pyx格式的文件,pyx文件就是我们Cython的源文件了,由于Cython是Python的超集,所以pyx文件里面完全可以写Python代码。
将如下Cython代码写入pyx文件,这里就叫example_fib.pyx
吧:
1 | cpdef int fib(int n): |
你会发现很奇妙的一点,Cython代码几乎和Python代码完全一样,不同的地方则是我们上面提到的重要的一点,需要将代码中的部分类型声明为Cython特定类型。
这里我们则是用cpdef int
进行了静态类型声明,定义了一个整型的函数。
注意: cpdef
表示python和cython都能调用,而cdef
定义的,只能cython调用,可以理解成是私有函数。
编译Cython代码
创建setup.py
文件用于编译,内容如下:
1 | from setuptools import setup |
接下来我们运行如下命令进行编译:python setup.py build_ext
build_ext
(build extensions)用于编译和构建 Python C 扩展模块,包括 .pyd
(Windows)或 .so
(Linux/macOS)文件。
完成编译后你会得到名字为build
的目录,以及自动生成的C代码example_fib.c
文件。
接下来我们需要找到编译好的扩展名为.pyd
的模块(在Linux/macOS平台,扩展名是.so
),然后将其复制到我们要使用的目录下。.pyd
位置在build目录下的lib.win-amd64-cpython-310
文件夹中,名字类似可能不完全相同,因为我使用的是win x64平台,python版本是3.10的环境,所以名字是这个。
我生成的.pyd
文件名为example_fib.cp310-win_amd64.pyd
我们在最上层目录创建一个新的文件夹(当然你可以在任何位置创建,文件夹的名字也是任意的),就叫做fib_calc_test
吧,意思就是斐波那契数列计算测试,将example_fib.cp310-win_amd64.pyd
复制到该文件夹以供我们python脚本调用。
P.S.: 也可以指定参数 -i/–inplace ,使其将生成的pyd文件自动复制到当前运行命令的目录。
python setup.py build_ext -i
运行测试
在fib_calc_test
文件夹中创建python脚本fib_calc.py
文件用于我们测试运行,内容如下:
这个时候你会发现你的代码编辑器会给你把example_fib库引用报红,不用担心,后面我们会解决这个问题。它并不影响正常运行,只是代码编辑器目前还不认识我们写的东西。
截止到目前,你的fib_calc_test
文件夹里面应该有两个文件了,分别是example_fib.cp310-win_amd64.pyd
和fib_calc.py
1 | import example_fib |
我的机器的输出结果如下(耗时单位是秒):
Python计算耗时: 43.804757595062256 计算结果: 165580141
Cython计算耗时: 0.7606198787689209 计算结果: 165580141
具体性能提升了多少,自己计算吧。
自动补全和代码编辑器识别
截止到这里,其实你已经会了Cython的使用,这里的内容其实是代码提示部分。
在上一步的测试过程中你也看到了,如果是跟着我所写的步骤做的,你会发现在你的代码编辑器里面有import相关的报红提示,但是代码却又可以正常运行,这是为什么呢?
原因是这样的,报红是由于代码编辑器无法识别到我们编译好的二进制pyd里面的函数,所以对于编辑器来说,它认为没有这个东西,便给你报红提示。
但是当你运行的时候,其实是python的解释器去主动引入,官方一点的词也就是Python的反射机制
。
(大白话:由于pyd文件跟我们的测试脚本文件在同一个目录下,在运行的时候数据加载到内存python可以从内存中找到想要的函数定义,所以运行正常。)
因此,我们要解决报红问题,其实也非常简单,我们知道二进制中一定有这个函数,所以我们只需要写点东西,告诉编辑器,有这个函数就ok啦。
pyi文件创建:
.pyi
文件(Python Interface Stub), 是python的类型声明文件,Cython生成的.pyd
由于没有源码可读,所以代码编辑器也不识别,开发者可以手动创建对应的.pyi
文件,来提供相关信息。
创建一个名为example_fib.pyi
的文件,并将其放入fib_calc_test
文件夹,注意保持跟你写的模块名的一致。内容如下:
截止到目前,你的fib_calc_test
文件夹里面应该有三个文件了,分别是example_fib.cp310-win_amd64.pyd
和fib_calc.py
以及example_fib.pyi
1 | def fib(num: int) -> int: ... |
这个时候你在返回你的fib_calc.py
文件,你会发现报红提示没有了,并且代码补全也正常了。
P.S.: 好多第三方库,你会发现点进去并不能看到源文件,只能看到一行函数的声明,明白为什么了吧,你看的其实是它的.pyi类型声明文件,而源代码已经被编译成不可读的二进制了
到这里就告一段落了。