• Python使用distutils/ctypes调用C


    转载自Python 从入门到深入-C 语言模块

    由于 Python 是解释型语言,非常适合处理 IO 密集型任务,但是不善于处理计算密集型任务,例如加解密,压缩解压缩、或者深度学习中的数据集构建等

    为了同时发挥 Python 快速开发和 C 语言的快速处理的优点,Python 支持调用 C 语言接口。实际上 Python 底层均是由 C 语言开发的,调用 C 语言接口是最基本的功能。通常有两种方式来与 C 语言开发的模块(win 上为 dll,linux 上为 so 文件)交互:

    • 原生方式,直接使用 Python-dev 开发包开发 C 模块。
    • 通过 ctypes 模块实现 Python 调用 C 接口。

    1. 原生方式

    原生方式直接调用Python-dev 开发包开发 C 模块,这种方式必须使用开发包中的接口函数开发,需要熟悉相关数据类型和函数(这些函数通常被称为包裹函数)。通过这些函数可以方便实现任意参数类型的接口,但是开发出的 .so 通常作为 Python 的专用模块,不可以被其他 C 应用程序调用。

    大部分依赖 C 模块的第三方软件库使用这种方式开发,例如科学计算中的 numpy,scipy 软件库。这里以一个累加函数为例,来阐述原生方式的开发流程。

    首先创建 C 语言文件 accumulate.c 和对应函数:

    #include <stdio.h>
    
    unsigned int accumulate(unsigned int n)
    {
      unsigned int sum = 0;
      unsigned int i = 0;
    
      for(; i <= n; i++)
        sum += i;
    
      return sum;
    }
    
    int main()
    {
      printf("%u", accumulate(100));
    }
    

    在进一步操作之前,我们必须保证这些函数是正确执行的,编译测试它们:

    $  make accumulate && ./accumulate
    5050
    

    为了让 Python 解析器 CPython 可以解析这里的 C 函数,需要使用 Python.h 头文件里面的类型和函数来封装我们的 C 语言函数和类型参数。这里需要安装 Python 编译环境,pythonx.y-dev 提供了相关头文件和库文件:

    $ sudo apt-get install python3.6-dev

    接着使用 Python.h 相关类型和函数包裹我们的 C 函数,以编译为 Python 接口模块:

    #include <Python.h>
    
    static unsigned int _accumulate(unsigned int n)
    {
      unsigned int sum = 0;
      unsigned int i = 0;
    
      for(; i <= n; i++)
        sum += i;
    
      return sum;
    }
    
    static PyObject *accumulate(PyObject* self, PyObject* args)
    {
      unsigned int n = 0;
    
      // 类型解析参考 https://docs.python.org/3/c-api/arg.html#c.PyArg_ParseTuple
      if(!PyArg_ParseTuple(args, "i", &n))
        return NULL;
    
      return Py_BuildValue("i", _accumulate(n));
    }
    
    static PyMethodDef accuMethods[] =
    {
      {"accumulate", accumulate, METH_VARARGS, "Calculate the accumulation of n"},
      {NULL, NULL, 0, NULL}
    
    };
    
    static PyModuleDef accuModule =
    {
      PyModuleDef_HEAD_INIT,
      "accuModule",                   // module name
      "accumulate module description",// module description
      -1,
      accuMethods
    };
    
    
    // 仅有的非 static 函数,对外暴露模块接口,PyInit_name 必须和模块名相同
    // only one non-static function
    PyMODINIT_FUNC PyInit_accuModule(void)
    {
      return PyModule_Create(&accuModule);
    }
    

    最后创建 setup.py 以编译目标 .so 文件,

    from distutils.core import setup, Extension
    
    module = Extension('accuModule', sources = ['accumulate.c'])
    
    setup(name = 'accuModule',
              version = '1.0',
              description = 'This is a demo package',
              ext_modules = [module])
    

    此时文件夹中包含源码文件 accumulate.c 和 setup.py,开始编译:

    $ python3.6 setup.py build
    running build
    running build_ext
    building 'accuModule' extension
    creating build
    creating build/temp.linux-i686-3.6
    ...
    

    编译后当前路径文件目录如下所示:

    $ tree
    .
    ├── accumulate.c
    ├── build
    │   ├── lib.linux-i686-3.6
    │   │   └── accuModule.cpython-36m-i386-linux-gnu.so # 目标模块文件
    │   └── temp.linux-i686-3.6
    │       └── accumulate.o
    └── setup.py
    

    最后安装模块:

    $ sudo python3.6 setup.py install --record install.txt
    running install
    running build
    running build_ext
    running install_lib
    copying build/lib.linux-i686-3.6/accuModule.cpython-36m-i386-linux-gnu.so \
            -> /usr/local/lib/python3.6/dist-packages
    running install_egg_info
    Writing /usr/local/lib/python3.6/dist-packages/accuModule-1.0.egg-info
    

    其实也可以直接执行Install,会自动先bulid的

    可以看到模块被安装在了 /usr/local/lib/python3.6/dist-packages 目录下。

    开发中我们可能要多次删除和安装模块,但是 setup.py 没有提供对应的下载命令,这里使用 record 记录了安装到所有文件,如果要卸载,直接删除 install.txt 中记录的文件即可:

    $ cat install.txt
    /usr/local/lib/python3.6/dist-packages/accuModule.cpython-36m-i386-linux-gnu.so
    /usr/local/lib/python3.6/dist-packages/accuModule-1.0.egg-info
    
    # 卸载软件包
    $ cat install.txt | xargs rm -rf
    

    最后测试模块,创建 test.py:

    from accuModule import accumulate
    
    print(accumulate(100))
    

    执行 python3.6 test.py 可以得到结果 5050。

    2. ctypes

    采用原生方式对每一个 C 语言接口进行打包是很繁琐的,为了简化接口调用,ctypes 模块提供了和 C 语言兼容的数据类型和函数来加载模块(dll或so)文件,因此在调用时不需对源文件做任何的修改。

    关于 ctypes 的更多信息参考 A foreign function library for Python

    #include <stdio.h>
    
    unsigned int accumulate(unsigned int n)
    {
      unsigned int sum = 0;
      unsigned int i = 0;
    
      for(; i <= n; i++)
        sum += i;
    
      return sum;
    }
    

    注意对外接口不可定义为 static 的,通过 gcc 编译得到 .so 文件:

    # Mac
    $ gcc -shared -Wl,-install_name,accumulate.so -o accumulate.so -fPIC accumulate.c
    
    # windows
    $ gcc -shared -Wl,-soname,adder -o accumulate.dll -fPIC accumulate.c
    
    # Linux
    $ gcc -shared -Wl,-soname,adder -o accumulate.so -fPIC accumulate.c
    

    修改 test.py 直接通过 ctypes 模块中的 CDLL 方法加载模块:

    from ctypes import *
    
    #load the module file
    accuModule = CDLL('./accumulate.so')
    print(accuModule.accumulate(100))
    

    ctypes 接口允许我们在调用 C 函数时使用Python 中默认的字符串型和整型。

    但是对于其他类型,例如布尔型和浮点型,必须要使用对应的 ctype 类型才可以。这种方法虽然简单,清晰,但是却有很大限制:例如,不能在 C 中对 Python 对象进行操作。

    个性签名:时间会解决一切
  • 相关阅读:
    HBase in Action前三章笔记
    关于视频YUV
    23种设计模式用英语怎样表达?
    UVA 10620
    工具,帮助我们更高效的工作
    VS下控制台执行保持(不要一闪而过)
    C语言之结构体
    Linux grep命令和正则表达式
    C#中的“静态”
    推荐一个计算机视觉图书:python计算机视觉编程
  • 原文地址:https://www.cnblogs.com/lfri/p/15620481.html
Copyright © 2020-2023  润新知