Python C API ---- Write Python C Extension and Call Python From C
文章目录
C 语言 转 Python
解析 Python 变量
函数: PyArg_ParseTuple
- 单个 python 变量转成 C 变量
- python tuple 转成 多个 C 变量
接口 (PyArg_ParseTuple(args, "ii", &num1, &num2)
- args: Python 所有参数
- "ii": Python 参数的类型与个数,相当于翻译模板, "ii" 代表两个 整数
- &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("")
| |
输出名称映射
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 7static 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 5void initConver_Test(void) // 初始化函数 { // 添加映射函数 Py_InitModule("Conver_Test", Conver_Test_Methods); }初始化函数命名规则, 添加模块初始化有一点需要注意:
- 该函数命名格式为“init+文件名”
- python 官方命名格式: “PyInit_name”
1 2 3 4 5PyMODINIT_FUNC PyInit_spam(void) { return PyModule_Create(&spammodule); }
编译成 Python 模块
直接编译 gcc
命令
| |
使用 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 语言
字符串
| |
验证可用 python3.6
1PyTuple_SetItem(r, 2, PyUnicode_FromString("three"));- 从 C string 转成 Python str.
调用函数
| |
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
| |
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 13void 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
Exception Error 带传递与控制
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)
| |
PyErr_Format
可以格式化的字符串
PyErr_Format(Error_type, "format: %d", num, …)
| |
PyErr_Occured
检查,之前执行的过程中是否,已经发生了 Exception
PyErr_Occured()
| |
捕获异常 catch Exception
PyErr_ExceptionMatches
===> python catch
PyErr_ExceptionMatches(PyExc_KeyError)
打印异常
PyErr_Print()
获取与输出异常
获取异常的所有相关变量
| |
PyErr_Fetch
获取 Python Exception 异常的所有信息
Exception_Value
- 异常实例
- PyObject_Str(Exception_Value) ===> str(YourException)
转化成字符串
===> str(YourException)
| |
类型判断
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)
| |
Python None
Py_None ===> Python None 变量
Py_RETURN_NONE
返回 None
引用计数问题
类型
- New, Stolen, Borrowed
教程
对引用的处理方式
三种
- 传递 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 8void 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 10void 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 9void 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")
| |
调用 Python 类与方法
| |
调用 Python 函数
| |
无参数
- PyObject_CallFunction(pFun, "", "")
- PyObject_CallFunction(pFun, NULL)
带参数 args
PyObjectCallFuction
使用类似 Py_BuildValue 的方式传入 args
PyObject_CallFunction(pFun, "format", arg1, arg2, …)
| |
PyObject_CallFunctionObjargs
直接逐个传入,PyObject 类型参数
注意不要忘记结尾的 “NULL”
PyObject_CallFunction(pFun, PyObject1, PyObject2, …, NULL)
| |
PyObject_CallObject
PyObject_CallObject(pObject, args)
带 args 和 kwargs
PyObject_Call(pfun, PyObject *args, PyObject *kwargs) 没有的参数,用 NULL 代替
| |
运行 Python 代码
| |
PyRun_ 前缀
PyRun_SimpleString
单行代码
cmake
| |
查找 cmake 帮助
| |
通过 cmake –help-module FindPythonLibs 查找 Module 的帮助文档,可以查看这个 Module 定义的变量的 “真实名称”
注意
- 这个真实名称很重要,大小写,版本号等,很容易错误(网上的人,也 很多写的是错误的。)
builtin Python 内置函数使用
在 Python 的 builtin 模块中,存储的都是 python 内建功能
| |
| |
PyArg_ParseTuple
https://docs.python.org/3.6/extending/extending.html#extracting-parameters-in-extension-functions
| |
注意
只能解析 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)format 中不能出现逗号 “,”
1 2 3 4 5// Python: ((1, 2), 3) // !!!!错误 PyArg_ParseTuple(arg, "(i, i)i", &obj) // 正确 PyArg_ParseTuple(arg, "(ii)i", &obj)最外层不能出现括号“()”
- 括号只能用于内部嵌套的 Python Tuple
1 2 3 4 5// Python: ((1, 2), 3) // !!!!错误 PyArg_ParseTuple(arg, "((ii)i)", &obj) // 正确 PyArg_ParseTuple(arg, "(ii)i", &obj)
文章作者
上次更新 2022-03-03 (5c64003)