• Extending(扩展)&Embeding(嵌入)python


          跨语言相互调用,一直是不同编程语言间代码交互Interop的难题,微软一直致力于给C++与C#找个理想的”翻译“,这么多年在语法语义(当然还应该包含编译器)和ABI(应用二进制接口)层面做了不少尝试,进而产生了C++\CLI,C++\CX和COM等技术产物,但这些产物如同现实中自然语言翻译一样,并不算太完美(java同其他语言交互的机制不太了解)。在这点上Python似乎把问题解决的很好,这也就是为什么Python会叫做胶水语言。正是由于python的这一特性,所以它被广泛用于自动化测试中。

    python与其他语言交互:

      1.使用ctypes 模块调用 C 动态库:如果被测试模块是以C语言编写,可以用Ctypes调用动态链接库,好处在于不用进行额外的开发,可以直接使用编译好的动态库。 ctypes 提供了完整的 C 类型封装,也支持自定义类型,大大减少在调用过程中的工作量。但C++的改名机制使得python调用C++编译的动态链接库很麻烦。

      2.使用原生态的Python 的扩展和嵌入( Extending &Embedding)机制:Python 提供了一套完整的Extending框架来使用 C/C++ 编写扩展库,可以很灵活的开发 C/C++ 扩展模块。这种方法的缺点是工作量比较大,需要为每一个方法编写接口(但通过 SWIG可以降低工作量高效的调用动态链接库)。通过Embedding机制则可以使用C/C++调用python代码进行交互。

          (Note:SWIG 是一种简化脚本语言与 C/C++ 接口的开发工具,通过包装和编译 C 语言程序来达到与脚本语言通讯目的的工具。它正是基于 Python 的扩展机制,自动生成接口文件,再编译成可以被 Python 调用的动态库扩展模。目前不打算去了解SWIG,写下来备忘。)

      3.Boost.Python:  Boot Python是一个为实现C++与Python无缝交互而编写的类库。它可以不借助任何工具仅凭C++编译器使C++类方法和对象快速无缝的暴露给Python,而目前(2.0版本)C++代码调用python模块还需要借助Python/C API不过已经很大程度减少了工作量。这个类库使用模板元编程技术简化了语法,包装C++代码只需要借助一种声明式接口定义语言declarative interface definition language (IDL)。 

      在Python 中引用 C/C++ 模块的方法较多,根据需要从中选择恰当的方法可以减少很多工作量。在Python 中引用 C/C++ 模块弥补了 Python 脚本测试框架的很多不足,在提高代码复用率的同时,模块的性能也大大提高。

    仔细阅读了《Extending and Embedding the python Interpreter》之后,想自己demo一下,做个学习日志,记录学习python的辛酸之路。

          无论是扩展(Extending)还是嵌入(Embeding),都需要python.h文件。PythonAPI定义了一系列的函数,宏,变量等(在python.h中定义的所有用户可见的符号都有个前缀py或者PY。)使得调用者可以调用Python运行时系统的大部分功能。这些Python API合成了一个C源文件,你可以通过Python.h访问这些对象。Python有可能会定义一些有可能影响到标准头文件的预处理程序,所以应在引用标准头文件前引用python.h。    

          需要指出的是嵌入和扩展有很多相像的地方(毕竟所谓交互就是提供一种翻译手段,将C能够认识的对象转化为Python所能认识,反之依然),他们主要的区别在于当扩展python时,你编写的代码还是在python解释器运行(你用C定义某个对象最终要转化成Python对象用python解释器运行),但嵌入python,跟python解释器关系就不大了(只是你程序的部分代码要调用python解释器去执行某些python代码完成交互)。相对来说扩展python比嵌入python更加直观好理解一点。Boost.Python可以简化你的工作,无论是嵌入,还是扩展。

          要想要自定义模块被python代码调用需要把模块内函数名和地址放入函数表Method Table 中。函数表必须被引用到模块定义结构module definition structure里。而模块定义结构必须在模块的初始化函数中被传递给解释器。初始化函数必须被命名成PyInit_name()。另外在扩展Python时经常会要自定义异常,对于自定义异常的初始化也应该放在初始化函数中完成。

         

    
    

    #include "Python.h"

    
    

    static PyObject *SpamError; // 自定义异常

    // 模块函数
    static PyObject *

    spam_system(PyObject *self, PyObject *args)
    {
    const char *command;
    int sts;

    
    

    if (!PyArg_ParseTuple(args, "s", &command))
    return NULL;
    sts = system(command);
    if (sts < 0) {
    PyErr_SetString(SpamError, "System command failed");
    return NULL;
    }
    return PyLong_FromLong(sts);
    }

    
    

    // 模块函数表
    static PyMethodDef SpamMethods[] = {
    {"system", spam_system, METH_VARARGS,
    "Execute a shell command."},
    {NULL, NULL}
    };

    //模块定义结构体

    static struct PyModuleDef spammodule = {
    PyModuleDef_HEAD_INIT,
    "spam",
    "example module doc string",
    -1,
    SpamMethods,
    NULL,
    NULL,
    NULL,
    NULL
    };

    
    

    PyMODINIT_FUNC
    PyInit_system(void)
    {
    PyObject *m;

    
    

    m = PyModule_Create(&spammodule);
    if (m == NULL)
    return NULL;

    
    

    SpamError = PyErr_NewException("spam.error", NULL, NULL);
    Py_INCREF(SpamError);
    PyModule_AddObject(m, "error", SpamError);
    return m;
    }

     Note:要使用同一个编译器去编译python build 和 这份代码,否则嵌入后不能够执行。使用VS2010编译python3.2源码中的PC\example_nt下代码时有需要改动工程文件,将两行initexample去掉,否则会编译出错,至于需要python2.6.lib或者python2.6_d.lib则完全可以使用同一份代码编译出3.2版本的lib(注意需要编译两个版本,debug版本对应后者,release版本对应前者)代替但同样需要改动工程文件。

          python解释器有个很重要的约定:当一个函数失败时,他应该设定异常条件并且返回一个error value(错误值)。异常会被存储在解释器里的一个静态的全局变量中,它是一个三元组(type, value, traceback):第一个值代表了异常类型,第二个全局变量会存储异常的关联值,第三个变量包含了异常堆栈的追溯对象。这个三元组就是Python中sys.exc_info()的返回值。如果这个三元组值为null,就代表没有异常发生。

          PyArg_ParseTuple():会检查参数类型,并把他们转化成C类型的值。

          python API定义了一组函数用于定义不同类型的异常:

      PyErr_SetString():它的参数是异常对象和一个C类型字符串,异常对象通常是一个预定义对象比如PyExc_ZeroDivisionError,C类型字符串表明了错误发生的原因,会被转换成python字符串对象并且会被当作异常的关联值存储起来。

      PyErr_SetFromErrno():只有一个异常参数,用于检查全局变量errno并构造关联值。

      PyErr_SetObject():两个参数,异常和关联值。 传递给这些函数的任何对象都不需要调用Py_INCREF()。

          PyErr_Clear():用于忽略掉函数失败引发的异常,只有一种情况C代码会掉用该函数:当不想传递异常错误信息给解释器而想自己完全处理异常。

    Reference:

         http://docs.python.org/py3k/extending/index.html

         http://www.boost.org/doc/libs/1_49_0/libs/python/doc/index.html

  • 相关阅读:
    330. Patching Array--Avota
    334. Increasing Triplet Subsequence My Submissions Question--Avota
    C++前置++与后置++的区别与重载
    OpenGL光源位置
    深度探索va_start、va_arg、va_end
    C++类型转换
    2019-2020-2 20175216 《网络对抗技术》Exp9 Web安全基础
    2019-2020-2 20175216 《网络对抗技术》Exp8 Web基础
    2019-2020-2 20175216 《网络对抗技术》Exp7 网络欺诈防范
    2019-2020-2 20175216 《网络对抗技术》Exp6 MSF基础应用
  • 原文地址:https://www.cnblogs.com/salomon/p/2524305.html
Copyright © 2020-2023  润新知