C 语言 转 Python

解析 Python 变量

  • 函数: PyArg_ParseTuple

    • 单个 python 变量转成 C 变量
    • python tuple 转成 多个 C 变量
    • 接口 (PyArg_ParseTuple(args, "ii", &num1, &num2)

      • args: Python 所有参数
      • "ii": Python 参数的类型与个数,相当于翻译模板, "ii" 代表两个 整数
      • &num1, &num2: 使用引用指针接收,解析出来的变量
    • 返回值: 失败为零, 成功非零
1
2
3
4
5
6
7
8
9
static PyObject * Conver_Test_Max_value(PyObject *self, PyObject *args)
{
    int num1, num2;
    // "ii"表示两个整型参数
    if(!(PyArg_ParseTuple(args, "ii", &num1, &num2))){
        return NULL;
    }
    return (PyObject *)Py_BuildValue("i", Max_value(num1, num2));
}

创建 Python 变量

  • 函数: Py_BuildValue

    • 接口: (PyObject *)Py_BuildValue("i", Max_value(num1, num2));

      • "i": 格式化字符串
      • 之后的变量: 传递参数
    • 类型转换: 一定要是 PyObject * 类型

创建 Python 函数

分成两部分

实现函数

  • 结构
  • 返回类型: static PyObject *
  • 接口: 总是(PyObject* self, PyObject *args)
  • 无返回值: 使用 Py_RETURN_NONE 或 (PyObject *)Py_BuildValue("")
1
2
3
4
5
6
7
8
9
static PyObject * Conver_Test_Max_value(PyObject *self, PyObject *args)
{
    int num1, num2;
    // "ii"表示两个整型参数
    if(!(PyArg_ParseTuple(args, "ii", &num1, &num2))){
        return NULL;
    }
    return (PyObject *)Py_BuildValue("i", Max_value(num1, num2));
}

输出名称映射

C 函数实现函数 与 Python 中使用名称的映射关系

  • 格式

    • 保存类型: static PyMethodDef, 数组
    • 数据格式:

      1
      2
      3
      4
      
      {
          {"Python中名称", "C 语言实现函数", "函数的调用方式", "帮助文档"},
          {NULL, NULL, 0, NULL},
      };
  • 函数调用方式

    • METH_VARARGS: 多个参数形式
    • METH_NOARGS:无参数,常用于输出类函数
    • METH_VARARGS|METH_KEYWORDS :同时拥有匿名参数和关键字参数
    • 例子

      1
      2
      3
      4
      5
      6
      7
      
      static PyMethodDef Conver_Test_Methods[] =
      {
       {"Table99", Conver_Test_Table99, METH_VARARGS},
       {"Fabocci", Conver_Test_Fabocci, METH_VARARGS},
       {"Max_value", Conver_Test_Max_value, METH_VARARGS},
       {NULL, NULL},
      };

模块初始化

把函数的映射关系,添加到模块

  • 添加映射函数: Py_InitModule

    • 接口: ("模块名", 函数映射关系)

      1
      2
      3
      4
      5
      
      void initConver_Test(void) // 初始化函数
      {
          // 添加映射函数
          Py_InitModule("Conver_Test", Conver_Test_Methods);
      }
    • 初始化函数命名规则, 添加模块初始化有一点需要注意:

      • 该函数命名格式为“init+文件名”
      • python 官方命名格式: “PyInit_name”
      1
      2
      3
      4
      5
      
      PyMODINIT_FUNC
      PyInit_spam(void)
      {
          return PyModule_Create(&spammodule);
      }

编译成 Python 模块

直接编译 gcc

命令

1
2
gcc -shared -fPIC Conver_Test.c -I/usr/include/python2.7/\
-L/usr/lib -lpython2.7 -o Conver_Test.so
使用 distutils, setup.py
  • setup.py 文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    #!/usr/bin/env pthon
    from distutils.core import setup, Extension
    
    MOD = 'Conver_Test'   #库文件名称
    
    #C文件名称
    example_module = Extension(MOD, sources=['Conver_Test.c'])
    
    setup(name=MOD ext_modules=[example_module])
  • 命令

    • python setup.py build

Python 转 C 语言

字符串

1
2
3
4
  PyObject * mypy_str = Py_BuildValue("s#", "good day", 4);
  const char * str;
  str = PyUnicode_AsUTF8(mypy_str);
  printf("good: %s", str);  // output good: good
  • 验证可用 python3.6

    1
    
      PyTuple_SetItem(r, 2, PyUnicode_FromString("three"));
    • 从 C string 转成 Python str.

调用函数

1
2
3
4
5
//直接构建参数args
PyObject *ret_str = PyObject_CallFunction(p_print, "s", "Lucy");

//无参数
PyObject_CallFunction(p_print, NULL);

Python C API 的返回值

Most C API functions also return an error indicator, usually NULL if they are supposed to return a pointer, or -1 if they return an integer (exception: the PyArg_*() functions return 1 for success and 0 for failure). https://docs.python.org/3/c-api/exceptions.html

打印问题

PyObject_Print(pLast, stdout, 0);

把 PyObject *pLast, 输出到 stdout, 0–> flags (控制输出选项功能 printing options).

PyObject_Str

相当于===> python, str(variable)

字符串类型

PyBytes_ 前缀

PyBytes_FromString

C ==> Python

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include "Python.h"

void print_hello_world(void) {
    PyObject *pObj = NULL;

    pObj = PyBytes_FromString("Hello world\n"); /* Object creation, ref count = 1. */
    PyObject_Print(pLast, stdout, 0);
    Py_DECREF(pObj);    /* ref count becomes 0, object deallocated.
                         ,* Miss this step and you have a memory leak. */
}

PyBytes_Check

PyUnicode_ 前缀

PyUnicode_AsUTF8

Python ==> C

PyUnicode_FromString

C ==> Python

Exception and Error

https://docs.python.org/3/c-api/exceptions.html

  • 注意

    • 假如,在代码的前面发生了异常,如果不清除异常,即使后面的代码是正 确的,也不能正常运行,类似 Python raise Exception 以后, Intepreter 解释器中止执行。
    • 因此,需要检查异常

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      
      void check_and_clean_error()
      {
        const char* str = NULL;
        if (PyErr_Occurred())
        {
          str = "true";
          PyErr_Clear();
        }
        else
          str = "false";
      
        printf("\nif error ocurred: %s!\n", str);
      }

Exception class types 异常类

PyExc_ 前缀

系统自带的异常类型

PyExc_ValueError

==> Python, ValueError

Raise 抛出异常

==> Python, raise 关键字

  • 注意

    • 定义的 Python 模块函数,要以返回 NULL, 来表明,这个函数内部有异 常。
    • 注意:这里的 NULL 表示异常,和“Py_RETURN_NONE”,意义不同
    • 返回 NULL: 和 C 语言对接
    • raise Exception: 和 Python 对接

      • 必须既要有“Raise Exception”, 也要返回 NULL。
    • 只返回 NULL 时:“ error return without exception set”
    • 只设置 Exception 时: 会发生 Runtime error

      • 即,定位发生 Exception 的位置,会错误的定位到,下面有返回 NULL 的函数内部,不是最初设定 Exception 的位置。
      • 重复设置多次 Exception, 以最后一条为准。

PyErr_SetString

单个普通字符串

PyErr_SetString(Error_type, string)

1
2
3
4
5
static PyObject *_raise_error(PyObject *module) {

PyErr_SetString(PyExc_ValueError, "Ooops.");
return NULL;
}

PyErr_Format

可以格式化的字符串

PyErr_Format(Error_type, "format: %d", num, …)

1
2
3
4
5
6
7
8
static PyObject *_raise_error_formatted(PyObject *module) {

    PyErr_Format(PyExc_ValueError,
                 "Can not read %d bytes when offset %d in byte length %d.", \
                 12, 25, 32
	     );
return NULL;
}

PyErr_Occured

检查,之前执行的过程中是否,已经发生了 Exception

PyErr_Occured()

1
2
3
4
5
6
static PyObject *_raise_error_mixup_test(PyObject *module) {
    if (PyErr_Occurred()) {
        return NULL;
    }
    Py_RETURN_NONE;
}

捕获异常 catch Exception

PyErr_ExceptionMatches

===> python catch

PyErr_ExceptionMatches(PyExc_KeyError)

打印异常

PyErr_Print()

获取与输出异常

获取异常的所有相关变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
PyObject* exc_type = NULL;
PyObject* exc_value = NULL;
PyObject* exc_stack = NULL;
PyObject_CallMethod(this->calcer, "set_temp", "(d, s)", temp, degree_unit.c_str());
if (PyErr_Occurred())
{
    cout<<"\nError happend!"<<endl;
    PyErr_Fetch(&exc_type, &exc_value, &exc_stack);

    PyObject* temp = NULL;
    temp = PyObject_Str(exc_value);
    this->error = PyUnicode_AsUTF8(temp);
}

PyErr_Fetch

获取 Python Exception 异常的所有信息

  • Exception_Value

    • 异常实例
    • PyObject_Str(Exception_Value) ===> str(YourException)

转化成字符串

===> str(YourException)

1
2
3
string error = "";
temp = PyObject_Str(exc_value);
error = PyUnicode_AsUTF8(temp);

类型判断

Python ===> isinstance(oject, the_type)

PyObject_TypeCheck

==> isinstance

PyObject_TypeCheck(object, given_type)

内建类型--判断

PyBytes_Check

isinstance(object, bytes)

PyBytes_Check(object)

获取类型

===> type(object)

Py_Type

获取类型(PyObject*) ===> type(object)

Py_Type(object)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
static PyObject*
function(PyObject *self, PyObject *arg) {
     /* ... */
    if (! PyBytes_Check(arg)) {
        PyErr_Format(PyExc_TypeError,
                     "Argument \"value\" to %s must be a bytes object not a \"%s\"",
                     __FUNCTION__, Py_TYPE(arg)->tp_name);
        goto except;
    }
    /* ... */
}

Python None

Py_None ===> Python None 变量

Py_RETURN_NONE

返回 None

引用计数问题

对引用的处理方式

  • 三种

    • 传递 pass it on
    • 存储 store it
    • 清理 call Py_DECREF()
  • 传递(pass it on)

    • 传递给 调用者

      • 具体所有权怎么交接,不定
  • 存储(store it)

    • 本地要把这个对象存储下来

      • 因此,要提高引用计数 Py_INCREF()
  • 清理

    • 不要了,降低计数

所有权规则 Ownership

  • 所有权类型

    • owned reference

      • 需要管理引用计数

        • store –> Py_INCREF()
        • clean –> Py_DECREF()
    • borrowed reference

      • 不用管理引用计数
  • 函数面临的应用管理

    • 接收参数
    • 返回参数

函数(引用传递)

  • 通常会在接口说明中,解释清除 所有权规则
返回参数
  • 情况一:全权交给调用者管理

    • 创建 PyObject 的函数

      • 相关函数

        • PyLong_FromLong()
        • Py_BuildValue()
      • 机理

        • 把所有权交给调用者
        • PyLong 类型,可能已经存在,返回的是已有 cache 的引用
    • 从已有对象中提取子对象

      • 相关函数

        • PyObject_GetAttrString
  • 情况二:Borrowed, 只使用,不拥有,不管理

    • PyTuple_GetItem()
    • PyList_GetItem()
    • PyDict_GetItem()
    • PyDict_GetItemString()
    • 特殊

      • PyImport_AddModule()

        • 虽然内部创建 模块,但是,这个模块被添加到 sys.modules
        • 所以相当于已经 Py_INCREF() 了
        • 因此,传递出去的是 Borrowed 引用
接收参数(形参)
  • 情况一:被调者收到的是 Borrowed 引用

    • 机理

      • 一般情况下,调用者,借用上级函数中的对象,上级函数中一直会 存在这个对象,即使被调者退出,这个对象也不会消失,因此不用 担心 借用过程中出现悬空指针的问题
  • 情况二:特殊情况, 需要存储

    • 机理

      • 因为要把传入对象存储到别的容器中,所以被调者要内部增加引用
      • 调用 Py_INCREF()
    • 相关函数

      • PyTuple_SetItem()
      • PyList_SetItem()

Borrowed 引用 使用误区 Thin-Ice

  • 参考

  • 案例一

    • 错误代码

      1
      2
      3
      4
      5
      6
      7
      8
      
      void
      bug(PyObject *list)
      {
          PyObject *item = PyList_GetItem(list, 0);
      
          PyList_SetItem(list, 1, PyLong_FromLong(0L));
          PyObject_Print(item, stdout, 0); /* BUG! */
      }
    • 改正代码

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      
      void
      no_bug(PyObject *list)
      {
          PyObject *item = PyList_GetItem(list, 0);
      
          Py_INCREF(item);
          PyList_SetItem(list, 1, PyLong_FromLong(0L));
          PyObject_Print(item, stdout, 0);
          Py_DECREF(item);
      }
    • 错误机理

      • list 包含 对所有元素的引用
      • 这里 list[1] = 0 实际过程

        • older_elem = list[1] 引用计数 count–
        • older_elem, 计数 count ==0

          • 触发 older_elem.__del__()
          • older_elem.__del__() 内部

            • 可能包含 删除 list[0] –> del list[0]

              • 即删除 item

                • 触发 item 被释放
                • —– 危险产生 ——
      • print(item)

        • item 已经被释放
        • 导致错误引用, 悬空指针
    • 解决办法

      • (1)对要添加到 容器 list 内部的 item,(2)并且后续又要再次使用的 item
      • 从 borrowed 类型 转成 owned 类型

        • 管理它的引用计数
  • 案例二

    • Py_BEGIN_ALLOW_THREADS
    • Py_END_ALLOW_THREADS
    • 释放 GIL 锁导致的问题
    • 错误代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      void
      bug(PyObject *list)
      {
          PyObject *item = PyList_GetItem(list, 0);
          Py_BEGIN_ALLOW_THREADS
          ...some blocking I/O call...
          Py_END_ALLOW_THREADS
          PyObject_Print(item, stdout, 0); /* BUG! */
      }
      • 原因分析

        • 在释放 gil 锁的过程中, list[0] 可能已经被修改过多次
      • 解决方法

        • 法一:使用和获取 item 代码合并到一起
        • 法二:管理 item 的引用计算,先 count++,后 count–

GIL 释放

  • 相关宏

    • Py_BEGIN_ALLOW_THREADS
    • Py_END_ALLOW_THREADS

调用 Python 模块 Import

PyImport_ImportModule("module name")

1
PyObject *pmod = PyImport_ImportModule("hello");

调用 Python 类与方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// python: hello.Hello 类
// python: hello.Hello.print 方法
// Use Hello class
// import needed modules
PyObject *pmod = PyImport_ImportModule("hello");

PyObject* pclass = PyObject_GetAttrString(pmod, "Hello");
PyObject* args = Py_BuildValue("(s)", "Zhou Hang");
// 实例初始化, no kwargs
PyObject* pinstance = PyObject_Call(pclass, args, NULL);
// 调用实例方法
PyObject_CallMethod(pinstance, "print", "", "");
PyObject_CallMethod(pinstance, "sum", "(ii)", 5, 4);

调用 Python 函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// import needed modules 导入模块
PyObject *pmod = PyImport_ImportModule("hello");


// get function from modules 导入函数名
PyObject *p_print = PyObject_GetAttrString(pmod, "say_hello");
// use the function from python


// 进行调用
PyObject *ret_str = PyObject_CallFunction(p_print, "s", "Lucy");
PyObject_CallFunction(p_print, NULL);

无参数

  • PyObject_CallFunction(pFun, "", "")
  • PyObject_CallFunction(pFun, NULL)

带参数 args

PyObjectCallFuction

使用类似 Py_BuildValue 的方式传入 args

PyObject_CallFunction(pFun, "format", arg1, arg2, …)

1
PyObject *ret_str = PyObject_CallFunction(p_print, "s", "Lucy");

PyObject_CallFunctionObjargs

直接逐个传入,PyObject 类型参数

注意不要忘记结尾的 “NULL”

PyObject_CallFunction(pFun, PyObject1, PyObject2, …, NULL)

1
2
3
4
5
PyObject* x = PyLong_FromLong(1L);
PyObject* y = PyLong_FromDouble(2.0);

ret = PyObject_CallFunctionObjArgs(pfun, x, y, NULL);
ret = PyObject_CallFunctionObjArgs(pfun, PyLong_FromLong(1L), PyFloat_FromDouble(2.0L), NULL);

PyObject_CallObject

PyObject_CallObject(pObject, args)

带 args 和 kwargs

PyObject_Call(pfun, PyObject *args, PyObject *kwargs) 没有的参数,用 NULL 代替

1
2
PyObject* args = Py_BuildValue("ii", 2, 3);
PyObject* ret = PyObject_Call(pfun, args, NULL);

运行 Python 代码

1
2
3
4
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.path.append('/home/sawyer/python3/c_use_python/demo01/python/')");

PyRun_SimpleString("print('run print from python3')");

PyRun_ 前缀

PyRun_SimpleString

单行代码

cmake

1
2
3
find_package(PythonLibs REQUIRED)
include_directories(${PYTHON_INCLUDE_DIRS})
target_link_libraries(<your exe or lib> ${PYTHON_LIBRARIES})

查找 cmake 帮助

1
2
3
4
5
cmake --help-module FindPythonLibs
cmake --help-module-list          # 列出所有的Module.
cmake --help-command find_package
cmake --help-command include_directories
cmake --help-command target_link_libraries

通过 cmake –help-module FindPythonLibs 查找 Module 的帮助文档,可以查看这个 Module 定义的变量的 “真实名称”

  • 注意

    • 这个真实名称很重要,大小写,版本号等,很容易错误(网上的人,也 很多写的是错误的。)

builtin Python 内置函数使用

在 Python 的 builtin 模块中,存储的都是 python 内建功能

1
2
import builtin
assert builtin.list == list
1
2
3
4
5
#include<Python.h>

// use python list() function.
PyObject* pBuiltin = PyImport_ImportModule("builtin");
PyObject* listFun = PyObject_GetAttrString(pBuiltin, "list")

PyArg_ParseTuple

https://docs.python.org/3.6/extending/extending.html#extracting-parameters-in-extension-functions

1
int PyArg_ParseTuple(PyObject *arg, const char *format, ...);

注意

  1. 只能解析 Python Tuple 类型

    • 若其中有嵌套,只能是解析被嵌套的 Python Tuple 序列内部的内容, 其它类型序列如:Python list, 只能当作一个主体(PyObject*)
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    // python: (1, 2)
    int a, b, c
    PyArg_ParseTuple(arg, "ii", &a, &b)
    
    PyObject* obj
    // python: ((1, 2), 3)
    PyArg_ParseTuple(arg, "(ii)i", &a, &b, &c)
    PyArg_ParseTuple(arg, "Oi", &obj, &a)
    
    // python: ([1, 2], 3)
    //**** 错误 ******
    PyArg_ParseTuple(arg, "[ii]i", &a, &b, &c)
    // 正确做法
    PyArg_ParseTuple(arg, "Oi", &obj)
  2. format 中不能出现逗号 “,”

    1
    2
    3
    4
    5
    
    // Python: ((1, 2), 3)
    // !!!!错误
    PyArg_ParseTuple(arg, "(i, i)i", &obj)
    // 正确
    PyArg_ParseTuple(arg, "(ii)i", &obj)
  3. 最外层不能出现括号“()”

    • 括号只能用于内部嵌套的 Python Tuple
    1
    2
    3
    4
    5
    
    // Python: ((1, 2), 3)
    // !!!!错误
    PyArg_ParseTuple(arg, "((ii)i)", &obj)
    // 正确
    PyArg_ParseTuple(arg, "(ii)i", &obj)