Cython - 入门简介

Python & C/C++

近来用Python用的越来越多,对这种十分灵活的动态语言的哲学也有了较深的理解。虽然Python有不少缺点,如没有强类型,GIL全局锁,没有编译因此效率底下,但正因为其动态性,它非常适合写软件的Prototype。因此一种典型而高效的工作流程是先用Python调用各种轮子,快速实现软件原型,然后再优化代码,将稳定的部分用C++或其他编译语言进行重写,变成一个供调用的库

在这个过程中就难免碰到Python与C++相互调用的问题,尤其体现在:

  1. 在原型开发阶段,如何将现有的C++库封装成Python能够方便调用的库,避免重造轮子
  2. 当原型开发结束后,如何将Python代码高效地转换成其他语言的代码

能够完成Python与C/C++相互操作的方式有很多,可以参见知乎的这篇专栏,但是本文介绍的Cython却鲜有详细或通俗的中文资料。Cython是个易用的Python扩展,在Anaconda等发行包里面都自带了,可以用Python的语法写出Python的C语言扩展。因此我选择这一款扩展库学习,之后也会把相关的学习经验写下来~

安装与基本使用

后文中的环境都是在 Windows10 + Visual Studio 2017 下

Cython在流行的Python发行版本中都有被包含,包括AnacondaCanopyPythonxySage等。如果使用普通的Python的话可以使用pip安装:pip install Cython

简单运行Cython代码

从语言层面来说,Cython是一种拓展的Python,其文件的扩展名为.pyx。这种类型的文件通过编译之后可以变成供Python直接调用的动态链接库(Linux/Mac下是.so,Windows下是.pyd)。根据官方文档,主要如下几编译方式:

  1. (推荐) 通过setup.py中调用Cython.Build进行编译
  2. 使用pyximport调用.pyx文件,这种方法.pyx文件相当于普通的.py文件
  3. 在命令行使用cython命令从.pyx文件生成.c文件,再使用外部编译器将.c文件编译成Python可用的库
  4. 使用Jupyter Notebook或者Sage Notebook直接运行Cython代码

这上面四种方法里最简单的是第三种方法。运行cythonize -i <.pyx File>即可编译.pyx成二进制库,并保存在与.pyx文件相同的目录下。cythonize命令有其他的参数,可以通过命令行查看。这个命令也可以通过python -m Cython.Build.Cythonize -i <.pyx File>来完成。

不过推荐使用的是第一种方法,原理也就是通过指定distutils或者setuptools库中的ext_modules参数来编译Cython代码。以官方示例为例,它的setup.py文件如下

1
2
3
4
5
6
7
from distutils.core import setup
from Cython.Build import cythonize

setup(
name = 'Hello world app',
ext_modules = cythonize("hello.pyx"),
)

其中hello.pyx的代码为

1
2
def say_hello_to(name):
print("Hello %s!" % name)

编辑保存之后直接运行python setup.py build_ext --inplace即可进行编译。其中--inplace参数可以让对应的链接库生成在源代码所在的目录。

编译中可能遇到的问题有

  • error: command 'cl.exe' failed: No such file or directory: 这说明在环境中没有找到C编译器。由于我电脑中安装了Visual Studio,我的解决方法是根据平台使用VS的x86 Native Command Promptx64 Native Command Prompt来运行编译命令。此外还可以选择通过python setup.py build_ext --inplace --compiler=mingw32使用Anaconda内置的MinGW32编译器,不过这种情况下还可能会遇到内置MinGW32的问题,具体的解决方法在链接里。

编译之后直接通过import hellofrom hello import say_hello_to即可调用这个编译好的库。

复杂Cython工程的setup.py

如果的Cython工程中有很多.pyx文件,甚至有很多.cpp文件需要编译,那么这时候最好使用setup.py进行编译,并使用Cython.Build.cythonize模块。具体编写方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize

extensions = [
Extension("Module Name", ["file1.pyx", "file2.cpp", ...],
include_dirs = [...],
libraries = [...],
library_dirs = [...]),
Extension("Module 2", ...)
]
setup(
name = "XXX",
ext_modules = cythonize(extensions),
)

最后的setup部分还有另一种写法:

1
2
3
4
5
setup(
name = "XXX",
ext_modules = extensions,
cmdclass={'build_ext': Cython.Build.build_ext}
)

这样的写法可以让安装整个库的时候一起执行掉Cython代码的编译和安装。此外,如果要在编译.pyx.cpp时指定语言或者编译参数,在Extension类的构造函数中添加合适的参数即可。

总结

本文介绍了Cython的安装与初步使用,看完以后就已经可以动手试试写Cython代码啦~Cython语言的特性也有不少,掌握其中的一小部分就能大大提高你的Python代码的运行效率!这些内容将会在之后的文章中讲述~

Cython的完整介绍可以参考Cython官网
Cython代码的编译还有其他方法,参见Cython文档的编译部分

Shoot me some coffee money XD