『Python学习笔记』使用Cython编程语言编译python文件

使用Cython编程语言编译python文件

一. 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 i
    cdef double a = 0.0, b = 1.0
    for i in range(n):
        a, b = a + b, a
    return 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 traceback

print(fib)  # <module 'fib' from '/data/aibox/kaifang/trans/cython/fib.so'>

# 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__)
# 这是一个扩展模块
<module 'fib' from '/data/aibox/kaifang/trans/cython/fib.so'>
Traceback (most recent call last):
  File "/data/aibox/kaifang/trans/cython/test_fib.py", line 16, in <module>
    print(fib.fib("xx"))  # 6765.0
  File "fib.pyx", line 14, in fib.fib
    for i in range(n):
TypeError: an integer is required

55.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; i<n; ++i) {
        tmp = a; a = a + b; b = tmp;
    }
   return a;
} // 函数体的实现
  • 然后是.pyx文件:
# 通过 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_cfib

print(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

参考文献

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI大模型前沿研究

感谢您的打赏,我会继续努力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值