• 『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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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  # 最后一个数字
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 然后我们对其进行编译:
    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代码生成扩展模块
    '''
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 编译后产生的文件: 这个文件叫做 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)  # 
    
    # 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__)
    # 这是一个扩展模块
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    <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
     这是一个扩展模块 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    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;
    } // 函数体的实现
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 然后是.pyx文件:
    # 通过 cdef extern from 导入头文件, 写上里面的函数
    cdef extern from "cfib.h":
        double cfib(int n)
    
    # 然后 Cython 可以直接调用
    def fib_with_c(n):
        """调用 C 编写的斐波那契数列"""
        return cfib(n)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 最后是编译:
    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))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

    • 然后我们来调用一下:
    # !/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 编写的斐波那契数列
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    6765.0
    调用 C 编写的斐波那契数列
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 我们看到成功调用 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    参考文献

  • 相关阅读:
    GraphQL 实践与服务搭建
    【基础知识】MPP架构和hadoop架构比对
    Web3钱包和身份验证:安全和去中心化的新标准
    UDS入门至精通系列:Service 27
    css高频面试题
    2022年华数杯C题插层熔喷非织造材料的性能控制研究
    数据标准详细概述-2022
    好书推荐之我读过的技术书v1
    Docker入门之安装Tomcat
    如何用vscode远程连接Linux服务器
  • 原文地址:https://blog.csdn.net/abc13526222160/article/details/128198895