Python的C扩展-学习总结

1. Python的C扩展

其实只要你懂得C语言编程,给Python添加新的内置(build-in)模块将十分容易。这些扩展(extension)模块可以实现两种无法直接在Python中进行的操作:他们可以实现新的内置对象类型,以及可以调用C语言的库函数和进行系统调用。

为了支持扩展,Python API定义了一个函数(functions)、宏命(macros)令和变量(variables)的集合,该集合提供了对Python运行时(run-time)系统的多方面的访问。Python API可以通过包含头文件Python.h的方式,整合进C语言源文件中。

注意: C语言的扩展接口指的是CPython,且扩展模块在其他Python实现上无效。在许多情况下,可以避免编写C扩展,并保留可移植性到其他实现上。例如,如果你的用例调用C库函数或进行系统调用,你可以考虑使用ctypes模块或cffi库而不是编写自定义的C代码。这些模块允许您编写Python代码来与C代码进行对接,并且Python实现比编写和编译C扩展模块更方便。
A Simple Example

我们准备构建一个名为spam的扩展模块,并假设我们想要为C库函数system()创建一个Python接口。该函数以null结尾(null-terminated)的字符串作为参数并返回一个整数。我们希望这个函数可以通过下列形式在Python中进行调用:

import spam
status = spam.system("ls -l")

我们首先新建一个spammodule.c文件。(一般来说,如果一个模块的名字叫spam,则实现它的C语言文件应该命名为spammodule.c;如果模块的名字非常长,就像spammify,那对应的文件名就可以直接写为spammify.c`。)

该文件的第一行为:

#include <Python.h>


这可以获取Python API。

注意:因为Python会定义一些预处理器的定义,这些定义会影响某些系统的标准头文件,所以你必须在文件的一开始包含Python.h文件。
然后添加函数的主体代码:

static PyObject * spam_system(PyObject *self, PyObject *args)  
{  
    const char *command;  
    int sts;  
    if (!PyArg_ParseTuple(args, "s", &command))  
        return NULL;  
    sts = system(command);  
    return PyLong_FromLong(sts);  
}  


其中PyArg_ParseTuple(args, “s”, &command)检测参数列表是否存在错误,错误则返回NULL,否则把指令传给command。

现在相当于我们已经实现了spam_system函数,接下来我们就要讨论如何让Python能调用它。

在这里我们的模块名称为spam,而spam_system是它的方法。所以我们需要把spam_system添加进spam的方法列表中。

static PyMethodDef SpamMethods[] = {  
    ...  
    {"system",  spam_system, METH_VARARGS,  
     "Execute a shell command."},  
    ...  
    {NULL, NULL, 0, NULL}        /* Sentinel */  
};  


其中,METH_VARARGS是参数传递的标准形式,它通过Python的元组在Python解释器和C函数之间传递参数。若采用METH_KEYWORD方式,则Python解释器和C函数之间将通过Python的字典类型在两者之间进行参数传递

system是函数spam_system在模块中的名字。

现在方法有了,我们需要构建一个表征该模块的结构体,然后将方法列表添加进去。如下所示:

static struct PyModuleDef spammodule = {  
   PyModuleDef_HEAD_INIT,  
   "spam",   /* name of module */  
   spam_doc, /* module documentation, may be NULL */  
   -1,       /* size of per-interpreter state of the module, 
                or -1 if the module keeps state in global variables. */  
   SpamMethods  
};  


定义完结构体后,就要定义初始化函数了。如下所示:

PyMODINIT_FUNC PyInit_spam(void)  
{  
    return PyModule_Create(&spammodule);  
}  


注意:在初始化函数中,一定要传入模块结构体。用PyModule_Create()函数初始化模块结构体。并且在Python 3中,初始化函数一定要用PyInit_name()的方式命名。初始化函数没有添加static声明,所以模块的初始化函数是模块的唯一对外接口。
上述材料都准备好后,我们接下来讨论如何将生成这个模块。我们编写一个setup.py文件,声明Extension,并用setuptools工具对模块进行打包。代码如下:

from setuptools import setup, Extension, distutils, Command, find_packages
C = Extension("spam",
              libraries=main_libraries,
              sources=['spammodule.c'],
              language='c',
              )

setup(name="spam", 
      version=1.0,
      ext_modules=[C],
      )


打开终端,cd到setup.py目录下,接着输入下面两条指令即可完成模块的编译。然后就可以用import指令对spam模块进行导入啦~

python setup.py build
python setup.py install 
在编译的过程中PyMODINIT_FUNC方法被调用,完成了spam的定义。

总结:让我们再来回顾一下整个过程:构建spammodule.c文件->#include <Python.h>及编写方法函数->构建某块结构体及初始化函数->构建setup.py文件,声明Extension及打包模块。

C++编写Python模块----相关设置

项目属性配置:

1.选择release + x64编译模式,否则编译dll会报错,(python 是64位)

2.如下表所述设置项目属性,然后选择“确定”。

Tab属性“值”
常规常规 > 目标名称指定想要在 from...import 语句中从 Python 引用的模块的名称。 定义 Python 的模块时,在 C++ 中使用相同的名称。 如果想要将项目的名称用作模块名称,请保留默认值 $(ProjectName)。
 常规 > 目标扩展名.pyd
 项目默认值配置类型动态库(.dll)
C/C++常规附加包含目录根据相应的安装添加 Python“include” 文件夹,例如 c:\Python36\include
C/C++预处理器预处理器定义将 Py_LIMITED_API; 添加到字符串(包括分号)的开头。 此定义会限制可从 Python 调用的某些函数,并使代码在 Python 不同版本之间更易于移植。
C/C++代码生成运行时库多线程 DLL (/MD)(请参阅下面的“警告”)
链接器 > 常规附加库目录根据相应的安装添加包含 .lib 文件的 Python“libs”文件夹,例如 c:\Python36\libs。 (务必指向包含 .lib 文件的“libs”文件夹,而非包含 .py 文件的 Lib 文件夹。)

 提示

     如果在项目属性中未看到 C/C++ 选项卡,这是因为项目不包含标识为 C/C++ 源文件的任何文件。 如果创建的源文件不含 .c 或 .cpp 扩展名,则可能出现这种情况。 例如,如果之前在“新建项”对话框中不小心输入了 module.coo(而不是 module.cpp),则 Visual Studio 会创建文件,但不会将文件类型设置为“C/C+ 代码”,而正是它激活 C/C++ 属性选项卡。即使将文件重命名为带 .cpp,此类识别错误仍会存在。 为了正确设置文件类型,请在“解决方案资源管理器”中右键单击文件,选择“属性”,然后将“文件类型”设置为“C/C++ 代码”。

 警告

     即使对于调试配置,也始终将“C/C++” > “代码生成” > “运行时库”选项设置为“多线程 DLL (/MD)”,因为此设置是生成非调试 Python 二进制文件时使用的设置。 如果碰巧设置了“多线程调试 DLL (/MDd)”选项,则生成调试配置会产生错误“C1189: Py_LIMITED_API 与 Py_DEBUG、Py_TRACE_REFS 和 Py_REF_DEBUG 不兼容”。 此外,如果删除 Py_LIMITED_API 来避免出现生成错误,则在尝试导入模块时,Python 会崩溃。 (如下所述,崩溃将发生在 DLL 对 PyModule_Create 的调用中,并出现输出消息“严重 Python 错误: PyThreadState_Get: 无当前线程”。)

/MDd 选项用于生成 Python 调试二进制文件(例如 python_d.exe),但对扩展 DLL 选择此选项仍会导致 Py_LIMITED_API的生成错误。

 

测试代码:

#include <Python.h> 

//C++函数
int Add(int x, int y)
{
    return x + y;
}

int Del(int x, int y)
{
    return x - y;
}


//***********************************************************接口函数,以接受和返回 Python 类型
static PyObject* WrappAdd(PyObject* self, PyObject* args)
{
    int x, y;
    if (!PyArg_ParseTuple(args, "ii", &x, &y))
    {
        return NULL;
    }
    return Py_BuildValue("i", Add(x, y));
}

static PyObject* WrappDel(PyObject* self, PyObject* args)
{
    int x, y;
    if (!PyArg_ParseTuple(args, "ii", &x, &y))
    {
        return NULL;
    }
    return Py_BuildValue("i", Del(x, y));
}


//*************************************************************模块方法表,向 Python 呈现 C++ 中方法、函数的结构
static PyMethodDef py_module_methods[] = {
        { "Add", WrappAdd, METH_VARARGS, "Execute a shell command." },
        { "Del", WrappDel, METH_VARARGS, "Execute a shell command." },
        { NULL, NULL, 0, NULL } /* Sentinel */
};


//**************************************************************模块的初始化函数

//--- #define PyMODINIT_FUNC extern "C" __declspec(dllexport) void
// PyMODINIT_FUNC 为宏定义,本身包含extern "C"
//extern "C"

PyMODINIT_FUNC initpy_module(void)
{    
    
    
    PyObject *m;
    m = Py_InitModule("py_module", py_module_methods);
    if (m == NULL)
        return;
    PyImport_AddModule("py_module");

}

/*

初始化函数的名称必须是init+模块名。这个函数必须是non-static的。
如动态库名字为py_module.pyd,则模块的初始化函数名必须是initpy_module,
且Py_InitModule()函数的第一个参数必须是“py_module”,否则Python导入模块会失败;

*/

2. PyTorch中的C拓展

_C模块的构建

在PyTorch中,很多类都会继承_C模块的内容。_C模块中包含了Tensor、Storage等常用类型的C语言实现。Tensor类型可以看做是_C模块中的一种类型对象,它们是包含与被包含的关系。

首先,我们先来看_C模块的实现。这个过程与第一部分讲的很类似。_C模块的主体声明定义代码在torch/csrc/Module.cpp中。该文件的一行也是#include <Python.h>,函数的方法实现有很多,这里先不讨论。接下来我们找到模块结构体的定义及初始化函数的声明。如下所示:

#if PY_MAJOR_VERSION == 2
PyMODINIT_FUNC init_C()
#else
PyMODINIT_FUNC PyInit__C()
#endif
{
    ...
#if PY_MAJOR_VERSION == 2
    ASSERT_TRUE(module = Py_InitModule("torch._C", methods.data()));
#else
    static struct PyModuleDef torchmodule = {
        PyModuleDef_HEAD_INIT,
        "torch._C",
        NULL,
        -1,
        methods.data()
    };
    ASSERT_TRUE(module = PyModule_Create(&torchmodule));
#endif
    ...
    ASSERT_TRUE(THPDoubleTensor_init(module));
    ASSERT_TRUE(THPFloatTensor_init(module));
    ASSERT_TRUE(THPHalfTensor_init(module));
    ASSERT_TRUE(THPLongTensor_init(module));
    ASSERT_TRUE(THPIntTensor_init(module));
    ASSERT_TRUE(THPShortTensor_init(module));
    ASSERT_TRUE(THPCharTensor_init(module));
    ASSERT_TRUE(THPByteTensor_init(module));
    ...
}    


注意:不同版本的Python,初始化函数的命名方式不同。源码中用#if、endif方式进行区分。为了方便理解,以Python 3为例。代码整理如下:

PyMODINIT_FUNC PyInit__C()
{
    ...
    static struct PyModuleDef torchmodule = {
        PyModuleDef_HEAD_INIT,
        "torch._C",
        NULL,
        -1,
        methods.data()
    };
    ASSERT_TRUE(module = PyModule_Create(&torchmodule));
    ...
    ASSERT_TRUE(THPDoubleTensor_init(module));
    ASSERT_TRUE(THPFloatTensor_init(module));
    ASSERT_TRUE(THPHalfTensor_init(module));
    ASSERT_TRUE(THPLongTensor_init(module));
    ASSERT_TRUE(THPIntTensor_init(module));
    ASSERT_TRUE(THPShortTensor_init(module));
    ASSERT_TRUE(THPCharTensor_init(module));
    ASSERT_TRUE(THPByteTensor_init(module));
    ...
}    



我们可以看到这里的声明和定义方式与之前有所不同。结构体的定义放在了初始化函数里,模块名称为torch._C。接着我们打开setup.py文件。

...
main_sources = [
    "torch/csrc/PtrWrapper.cpp",
    "torch/csrc/Module.cpp",
    "torch/csrc/Generator.cpp",
    "torch/csrc/Size.cpp",
    "torch/csrc/Exceptions.cpp",
    "torch/csrc/Storage.cpp",
    "torch/csrc/DynamicTypes.cpp",
    "torch/csrc/byte_order.cpp",
    "torch/csrc/utils.cpp",
    "torch/csrc/expand_utils.cpp",
    "torch/csrc/utils/invalid_arguments.cpp",
    ...
]
main_sources += split_types("torch/csrc/Tensor.cpp")
...
C = Extension("torch._C",
              libraries=main_libraries,
              sources=main_sources,
              language='c++',
              extra_compile_args=main_compile_args + extra_compile_args,
              include_dirs=include_dirs,
              library_dirs=library_dirs,
              extra_link_args=extra_link_args + main_link_args + [make_relative_rpath('lib')],
              )
extensions.append(C)
...
setup(name="torch", version=version,
      description="Tensors and Dynamic neural networks in Python with strong GPU acceleration",
      ext_modules=extensions,
      cmdclass=cmdclass,
      packages=packages,
      package_data={'torch': [
          'lib/*.so*', 'lib/*.dylib*',
          'lib/torch_shm_manager',
          'lib/*.h',
          'lib/include/TH/*.h', 'lib/include/TH/generic/*.h',
          'lib/include/THC/*.h', 'lib/include/THC/generic/*.h',
          'lib/include/ATen/*.h',
      ]},
      install_requires=['pyyaml', 'numpy'],
      )


可以看到和上一节的过程基本一样,这里就不再赘述了。在编译的过程中。Module.cpp中的PyMODINIT_FUNC函数被调用,完成torch._C的定义,及各种类型Tensor的初始化函数的调用。如:ASSERT_TRUE(THPDoubleTensor_init(module))。

注意:上述代码中的main_sources里还包含了Tensor.cpp这个文件。它主要包含了多类Tensor的实现。

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: C语言、Go语言和Python语言都是编程语言,它们各有不同的特点和应用场景。 C语言是一种高效的系统编程语言,广泛应用于操作系统、嵌入式系统、游戏开发等领域。C语言的语法简洁,性能优异,可以直接操作内存,但需要开发者手动管理内存,容易出现内存泄漏等问题。 Go语言是由Google开发的一种编程语言,主要用于网络编程、分布式系统和云计算领域。Go语言的语法简洁,具有并发编程的特性,支持垃圾回收,可以有效地避免内存泄漏等问题。 Python语言是一种高级的脚本语言,适用于科学计算、数据处理、Web开发等领域。Python语言的语法简单易学,具有丰富的标准库和第三方库,能够快速地完成开发任务,但相对于C语言和Go语言而言,性能较低。 综上所述,C语言适用于高性能、低级别的系统编程;Go语言适用于网络编程、分布式系统和云计算;Python语言适用于快速开发、科学计算等领域。 ### 回答2: C语言、Go语言和Python语言是三种不同的编程语言,它们在以下方面有所不同。 首先,C语言是一种面向过程的语言,它是一种编译型语言。C语言注重效率和对硬件的直接控制,并且有着较高的执行速度。它适用于底层开发,例如嵌入式系统、操作系统等。 其次,Go语言是由Google开发的,它融合了面向对象的编程思想和并发编程的概念。Go语言的特点是简洁、高效和易于学习,它提供了内置的并发原语和垃圾回收机制,使得编写并发程序更加方便。Go语言适用于网络编程、分布式系统等领域。 最后,Python语言是一种多范式的编程语言,它既支持面向对象编程,也支持函数式编程和命令式编程。Python语言注重代码的可读性和简洁性,具有丰富的标准库和第三方库,适用于快速开发和原型设计。Python语言广泛用于数据科学、人工智能等领域。 总结来说,C语言注重效率和直接控制硬件,适用于底层开发;Go语言简洁高效,注重并发编程,适用于网络编程和分布式系统;Python语言多范式、易读易写,适用于快速开发和数据科学。不同的语言适用于不同的领域,根据具体需求选择合适的语言。 ### 回答3: C语言、Go语言和Python语言是三种不同的编程语言,它们具有以下区别。 1. 语法和结构:C语言是一种过程化的编程语言,其语法相对复杂,需要手动管理内存。Go语言是一种现代化的静态类型编程语言,语法相对简单,具有垃圾回收机制,可以自动管理内存。Python语言是一种解释型的动态类型编程语言,语法简洁易读。 2. 应用领域:C语言被广泛用于系统级编程、嵌入式开发和性能要求较高的应用。Go语言适用于并发编程和网络编程,并具有良好的性能。Python语言适用于数据分析、机器学习、Web开发等领域,因为其语法简洁且具有大量的扩展库。 3. 性能:C语言的执行效率高,适合开发性能要求严格的应用。Go语言具有内置的并发机制和垃圾回收机制,因此在并发编程和内存管理方面有较好的性能。Python语言在执行效率方面相对较低,但具有丰富的扩展库和易用性。 4. 开发效率:C语言需要手动管理内存和处理复杂的指针操作,开发过程相对繁琐。Go语言具有简洁的语法和自动内存管理,开发效率较高。Python语言具有简洁的语法和丰富的扩展库,开发效率较高。 总之,C语言适合需要高性能和底层控制的应用,Go语言适合并发编程和网络编程,Python语言适合快速开发和数据处理。选择使用哪种语言取决于具体的应用场景和开发需求。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

隨意的風

如果你觉得有帮助,期待你的打赏

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

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

打赏作者

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

抵扣说明:

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

余额充值