• 『Python CoolBook』C扩展库_其六_从C语言中调用Python代码


    点击进入项目

    一、C语言运行pyfun的PyObject对象

    思路是在C语言中提供实参,传给python函数:

    • 获取py函数对象(PyObject),函数参数(C类型)
    • 获取GIL(PyGILState_Ensure)
    • 确保fun对象可调用
    • 参数转换为python对应类型(Py_BuildValue)
    • 调用python函数(PyObject_Call)
    • 确定调用无异常
    • 检查返回值
    • 释放GIL(PyGILState_Release)
    • 异常处理
    #include "Python.h"
    
    /* Execute func(x,y) in the Python interpreter.  The
       arguments and return result of the function must
       be Python floats */
    
    double call_func(PyObject *func, double x, double y) {
      PyObject *args;
      PyObject *kwargs;
      PyObject *result = 0;
      double retval;
    
      /* Make sure we own the GIL */
      PyGILState_STATE state = PyGILState_Ensure();
    
      /* Verify that func is a proper callable */
      /* 你必须先有一个表示你将要调用的Python可调用对象。 这可以是一个函数、
         类、方法、内置方法或其他任意实现了 __call__() 操作的东西。 为了确
         保是可调用的,可以像下面的代码这样利用 PyCallable_Check() 做检查 */
      if (!PyCallable_Check(func)) {
        fprintf(stderr,"call_func: expected a callable
    ");
        goto fail;
      }
      /* Build arguments */
      /* 使用 Py_BuildValue()构建参数元组或字典 */
      args = Py_BuildValue("(dd)", x, y);
      kwargs = NULL;
    
      /* Call the function */
      /* 使用 PyObject_Call(),传一个可调用对象给它、一个参数元组
         和一个可选的关键字字典。
         如果没有关键字参数,传递NULL */
      result = PyObject_Call(func, args, kwargs);
      /* 需要确保使用了 Py_DECREF() 或者 Py_XDECREF() 清理参数。 
         第二个函数相对安全点,因为它允许传递NULL指针(直接忽略它), 
         这也是为什么我们使用它来清理可选的关键字参数。 */
      Py_DECREF(args);
      Py_XDECREF(kwargs);
    
      /* Check for Python exceptions (if any) */
      /* 调用万Python函数之后,用PyErr_Occurred() 函数检查是否
         有异常发生 */
      if (PyErr_Occurred()) {
        PyErr_Print();
        goto fail;
      }
    
      /* Verify the result is a float object */
      if (!PyFloat_Check(result)) {
        fprintf(stderr,"call_func: callable didn't return a float
    ");
        goto fail;
      }
    
      /* Create the return value */
      retval = PyFloat_AsDouble(result);
      Py_DECREF(result);
    
      /* Restore previous GIL state and return */
      PyGILState_Release(state);
      return retval;
    
    fail:
      Py_XDECREF(result);
      PyGILState_Release(state);
      abort();   // Change to something more appropriate
    }
    

     要注意的是每一个 PyGILState_Ensure() 调用必须跟着一个匹配的 PyGILState_Release() 调用——即便有错误发生。 在这里,我们使用一个 goto 语句看上去是个可怕的设计, 但是实际上我们使用它来讲控制权转移给一个普通的exit块来执行相应的操作。 在 fail: 标签后面的代码和Python的 fianl: 块的用途是一样的。

    二、使用模块名和方法名获取pyfun的PyObject对象

    • 获取模块名字符串,方法名字符串
    • 模块名转化为python的字符串类型(PyUnicode_FromString)
    • 模拟python的import行为(PyImport_Import),这是因为我们想经由python的逻辑获取函数
    • 由python的module获取方法(PyObject_GetAttrString),这个API获取方法使用的是C字符串
    • 返回方法,时python的对象类型
    /* Load a symbol from a module */
    PyObject *import_name(const char *modname, const char *symbol) {
      PyObject *u_name, *module;
      u_name = PyUnicode_FromString(modname);
      module = PyImport_Import(u_name);
      Py_DECREF(u_name);
      return PyObject_GetAttrString(module, symbol);
    }
    

    三、C模拟Python运行

    • 初始化python环境(Py_Initialize)
    • 导入模块获取方法(见本文第二部分)为PyObject
    • 调用方法PyObject(见本文第一部分)
    • 结束python环境(Py_Finalize)
    /* Simple embedding example */
    int main() {
      PyObject *pow_func;
      double x;
    
      Py_Initialize();
      /* Get a reference to the math.pow function */
      pow_func = import_name("math","pow");
    
      /* Call it using our call_func() code */
      for (x = 0.0; x < 10.0; x += 0.1) {
        printf("%0.2f %0.2f
    ", x, call_func(pow_func,x,2.0));
      }
      /* Done */
      Py_DECREF(pow_func);
      Py_Finalize();
      return 0;
    }
    

    编译运行,

    gcc -g embed.c -I/home/hellcat/anaconda3/include/python3.6m           -L/home/hellcat/anaconda3/lib/python3.6/config-3.6m-x86_64-linux-gnu -lpython3.6m
    

    四、将可调用PyObject用C重新封装调用

    这是个意义不大功能,只是展示C API中PyObject本质运行逻辑——PyObject可以代指任何Python中的对象,这里是它接收函数的例子:

    /* Extension function for testing the C-Python callback */
    static PyObject *py_call_func(PyObject *self, PyObject *args) {
      PyObject *func;
    
      double x, y, result;
      if (!PyArg_ParseTuple(args,"Odd", &func,&x,&y)) {
        return NULL;
      }
      result = call_func(func, x, y);
      return Py_BuildValue("d", result);
    }
    

    把它写到前一节中的pysample.c中,有如下效果

    >>> import sample
    >>> def add(x,y):
    ...     return x+y
    ...
    >>> sample.call_func(add,3,4)
    7.0
    >>>
  • 相关阅读:
    约数个数 和 约数之和
    二分模板
    新生赛补题
    codefores刷题心得3 思维+dp(特别好玩)
    二叉树的遍历及例题
    团队作业七——团队作业分配
    WarPlane——游戏设计文档
    团队作业(五)
    团队作业(四)
    团队项目方案分析
  • 原文地址:https://www.cnblogs.com/hellcat/p/9093602.html
Copyright © 2020-2023  润新知