使用C扩展Python
- PyErr_*()函数是将一个异常对象压入到Python解释器的异常栈中
- PyErr_Clear()函数是将Python异常栈中栈顶的元素弹出, 调用这个函数通常就相当于在Python程序中的try: except: 语句中except的作用
- Python中的API, 每一个模块对应的API是PyMODNAME_FUNCINMOD()
- Python扩展模块的函数模板:
| |
| |
| |
| static PyObject *exfunc(PyObject *self, PyObject *args) { |
| ... |
| |
| |
| |
| |
| |
| |
| if (!PyArgs_ParseTuple(args, "format_string", &the_addr_of_c_variables)) { |
| return NULL; |
| } |
| |
| |
| |
| |
| |
| |
| ... |
| } |
| |
| |
| |
| |
| static PyMethodDef exfunc[] = { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| {"MethodName", your_func_pointer, METH_VARGS or METH_VARGS | METH_KEYWORDS, "Documents here"}, |
| {NULL, NULL, 0, NULL} |
| }; |
| |
| |
| static struct PyModuleDef exmodule = { |
| PyMODULEDEF_HEAD_INIT |
| "name", |
| "doc", |
| size, |
| exfunc, |
| }; |
| |
| |
| |
| |
| |
| |
| |
| PyMODINIT_FUNC PyInit_exmodule { |
| |
| PyObject *m; |
| |
| |
| |
| |
| |
| |
| m = PyModule_Create(&the_addr_of_PyModuleDef_array); |
| if (!m) { |
| return NULL; |
| } |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| PyMODINIT_FUNC initname { |
| |
| |
| PyInit_Module("name", MethodDef); |
| } |
| |
| |
| |
- 最好不直接使用malloc()函数, 如果该函数出现了错误, 应该调用PyErr_NoMemory()函数, 调用Python的API如PyLong_FromLong可以不考虑这个问题, 因为他底层已经封装了该异常
- Python大多数函数, 以正数或者0表示成功, 以NULL或者-1表示失败, 官方手册中
| PyArg_ParseTuple() and friends, functions that return an integer status usually |
| return a positive value or zero for success and -1 for failure |
- 如果扩展模块中的函数不希望有返回值的话, 返回Py_None对象
| Py_INCREF(Py_None); |
| return Py_None; |
- 为NULL返回值的函数在Python中视为发生了异常的函数
- 除了Python解释器要调用的PyMODINIT_FUNC PyInit_name()没有static修饰, 其他所有的函数和变量都是要statis修饰的
- PyMODINIT_FUNC PyInit_name()函数的名字必须是PyInit_开头的, 并且返回模块对象, 这样在Python中sys.modules序列才能捕捉到该引用, 将其添加到sys.modules这个全局的模块池中
- 当在Python中使用import extend_modules时, Python解释器就会调用C层面上的PyInit_extend_modules()函数, 在该函数中执行PyModule_Create创建模块, 并且加载module table中定义的函数集合
- 可是这样说, Python中那么多的built in模块都是Guido按照自己规定的API扩展出来的, 所以我们编写的扩展模块也可以称之为built in模块
- Python中的heapq, functools等内置模块的源码在Modules/xxmoudle.c
- PyCallable_Check()判断是否一个PyObject *对象可以调用
- PyObject_CallObject(my_callback, arglist); 第一个参数是PyObject *, 第二个参数就是调用该PyObject *对象需要的参数, 也为一个PyObject *(是一个Tuple对象), 这是没有关键词参数的情况; 使用PyObject_Call(my_callback, argslist, argkw); 可以有位置参数和关键词参数
- 使用Py_BuildValue()函数创建对象是官方建议的
- 每次调用一个Python API, 需要判断其返回值, 判断是否会发生异常, 所有C编写扩展模块会比较冗长
- 参数就是一个PyTupleObject, 用户创建一个参数列表:
| PyObject *arglist; |
| arglist = P_BuildValue("(i, l)", 8, 8888888) |
- 慎用Py_INCREASE, Py_DECREASE, Py_XINCREASE, Py_XDREASE
- 我们自己创建的参数列表, 在放入到一个函数中执行完之后, 使用Py_DREASE宏减少参数列表的引用计数, 说白了就是让这个Tuple消失, 因为已经没有用了
| int PyArg_ParseTupleAndKeywords(PyObject *arg, PyObject *kwdict, const char *format, char *kwlist[], ...); |
| arg: tuple对象 |
| kwdict: dict对象 |
| format: 字符串格式 |
| kwlist: 存放关键词参数的键, 最后为NULL表示结束 |
| |
| |
| NOTE!! 此函数只能借此keyword而不能解析位置参数了 |
- PyBuild_Value()函数
| Py_BuildValue("") None |
| Py_BuildValue("i", 123) 123 |
| Py_BuildValue("iii", 123, 456, 789) (123, 456, 789) |
| Py_BuildValue("s", "hello") 'hello' |
| Py_BuildValue("y", "hello") b'hello' |
| Py_BuildValue("ss", "hello", "world") ('hello', 'world') |
| Py_BuildValue("s#", "hello", 4) 'hell' |
| Py_BuildValue("y#", "hello", 4) b'hell' |
| Py_BuildValue("()") () |
| Py_BuildValue("(i)", 123) (123,) |
| Py_BuildValue("(ii)", 123, 456) (123, 456) |
| Py_BuildValue("(i,i)", 123, 456) (123, 456) |
| Py_BuildValue("[i,i]", 123, 456) [123, 456] |
| Py_BuildValue("{s:i,s:i}", |
| "abc", 123, "def", 456) {'abc': 123, 'def': 456} |
| Py_BuildValue("((ii)(ii)) (ii)", |
| 1, 2, 3, 4, 5, 6) (((1, 2), (3, 4)), (5, 6)) |
- 我们知道Python知道用特定的Py_Init, 他是如何实现的呢, 其实使用了宏中的
#
, 在宏定义中,使用 # 将参数转为字符串
- 如果是借来的引用的话, 是不会增加该对象的引用计数的, 但是在借来的基础上, 使用Py_INCREASE增加它的引用计数, 可以把借来的转为自己持有的
- 几乎所有的Python创建PyObject对象的API都会返回一个拥有的引用, 同时大多数的从一个对象中取出另一个对象的函数API也都是拥有的引用, 例外的PyTuple_GetItem(), PyList_GetItem(), PyDict_GetItem(), PyImport_AddModule() and PyDict_GetItemString()等函数则是返回借来的对象
- 传入参数到Python的API中, 大多数的参数对象都是不会增加引用的, 但是PyTuple_SetItem() and PyList_SetItem()是例外
- 对24和25的补充, 使用借来的引用, 它的存活周期是一个PythonCFunction, 如果需要对象存起来的话, 就会调动Py_INCREASE增加引用计数, 但是如果要返回一个Python对象的话, 需要增加其引用计数
- 一般PythonAPI不怎么检验Python对象为NULL的情况
- 模块的一些初始化操作在PyInit_函数中进行
- 如果希望添加新的内置类型, 这个我最熟悉(那是多么痛的领悟), 因为我写过, 定义一个type类型结构体, 第一个是PyObject_HEAD, 接着在初始化type(一定要有.tp_flags = Py_TPFLAGS_DEFAULT,), 在PyInit_中将该类型添加到模块对象中
- 在扩展模块中设置的名称为modulename.subname
| This initializes the Custom type, filling in a number of members to the appropriate default values, including ob_type that we initially set to NULL. |
| if (PyType_Ready(&CustomType) < 0) |
| return; |
| |
| |
| This adds the type to the module dictionary. |
| PyModule_AddObject(m, "Custom", (PyObject *) &CustomType); |
| |
- 一个完整的type赋值, 根据1, 2, 3得知, 为什么每次创建一个类型都会有new, init, del, 因为在其对应的PyTypeObject中需要指明, 并且不是通过PyMethodDef表控制的
| static PyTypeObject CustomType = { |
| PyVarObject_HEAD_INIT(NULL, 0) |
| .tp_name = "custom2.Custom", |
| .tp_doc = "Custom objects", |
| .tp_basicsize = sizeof(CustomObject), |
| .tp_itemsize = 0, |
| .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, |
| .tp_new = Custom_new, |
| .tp_init = (initproc) Custom_init, |
| .tp_dealloc = (destructor) Custom_dealloc, |
| .tp_traverse = (traverseproc) Custom_traverse, |
| .tp_clear = (inquiry) Custom_clear, |
| .tp_members = Custom_members, |
| .tp_methods = Custom_methods, |
| .tp_getset = Custom_getsetters, |
| }; |
| |
| |
| |
| static int |
| Custom_traverse(CustomObject *self, visitproc visit, void *arg) |
| { |
| Py_VISIT(self->first); |
| Py_VISIT(self->last); |
| return 0; |
| } |
| |
| static int |
| Custom_clear(CustomObject *self) |
| { |
| Py_CLEAR(self->first); |
| Py_CLEAR(self->last); |
| return 0; |
| } |
- 如何要继承一个类型的话, 那么定义的类型的第一行就是那个类型对象而不是HEAD了, 那个类型对象中已经包含了HEAD了, 所有新建的对象就可以转换成新建的这个对象类型或者他的父类型, 同时在Init函数中需要指定该类型对象的tp_base是谁, 这样在Python层面上实现集成
Demo file, according to the official
| #include <Python.h> |
| |
| typedef struct { |
| PyListObject list; |
| int state; |
| } SubListObject; |
| |
| static PyObject * |
| SubList_increment(SubListObject *self, PyObject *unused) |
| { |
| self->state++; |
| return PyLong_FromLong(self->state); |
| } |
| |
| static PyMethodDef SubList_methods[] = { |
| {"increment", (PyCFunction) SubList_increment, METH_NOARGS, |
| PyDoc_STR("increment state counter")}, |
| {NULL}, |
| }; |
| |
| static int |
| SubList_init(SubListObject *self, PyObject *args, PyObject *kwds) |
| { |
| if (PyList_Type.tp_init((PyObject *) self, args, kwds) < 0) |
| return -1; |
| self->state = 0; |
| return 0; |
| } |
| |
| static PyTypeObject SubListType = { |
| PyVarObject_HEAD_INIT(NULL, 0) |
| .tp_name = "sublist.SubList", |
| .tp_doc = "SubList objects", |
| .tp_basicsize = sizeof(SubListObject), |
| .tp_itemsize = 0, |
| .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, |
| .tp_init = (initproc) SubList_init, |
| .tp_methods = SubList_methods, |
| }; |
| |
| static PyModuleDef sublistmodule = { |
| PyModuleDef_HEAD_INIT, |
| .m_name = "sublist", |
| .m_doc = "Example module that creates an extension type.", |
| .m_size = -1, |
| }; |
| |
| PyMODINIT_FUNC |
| PyInit_sublist(void) |
| { |
| PyObject *m; |
| SubListType.tp_base = &PyList_Type; |
| if (PyType_Ready(&SubListType) < 0) |
| return NULL; |
| |
| m = PyModule_Create(&sublistmodule); |
| if (m == NULL) |
| return NULL; |
| |
| Py_INCREF(&SubListType); |
| PyModule_AddObject(m, "SubList", (PyObject *) &SubListType); |
| return m; |
| } |
| |
Another Demo
| #include <Python.h> |
| #include "structmember.h" |
| |
| typedef struct { |
| PyObject_HEAD |
| PyObject *first; |
| PyObject *last; |
| int number; |
| } CustomObject; |
| |
| static void |
| Custom_dealloc(CustomObject *self) |
| { |
| Py_XDECREF(self->first); |
| Py_XDECREF(self->last); |
| Py_TYPE(self)->tp_free((PyObject *) self); |
| } |
| |
| static PyObject * |
| Custom_new(PyTypeObject *type, PyObject *args, PyObject *kwds) |
| { |
| CustomObject *self; |
| self = (CustomObject *) type->tp_alloc(type, 0); |
| if (self != NULL) { |
| self->first = PyUnicode_FromString(""); |
| if (self->first == NULL) { |
| Py_DECREF(self); |
| return NULL; |
| } |
| self->last = PyUnicode_FromString(""); |
| if (self->last == NULL) { |
| Py_DECREF(self); |
| return NULL; |
| } |
| self->number = 0; |
| } |
| return (PyObject *) self; |
| } |
| |
| static int |
| Custom_init(CustomObject *self, PyObject *args, PyObject *kwds) |
| { |
| static char *kwlist[] = {"first", "last", "number", NULL}; |
| PyObject *first = NULL, *last = NULL, *tmp; |
| |
| if (!PyArg_ParseTupleAndKeywords(args, kwds, "|UUi", kwlist, |
| &first, &last, |
| &self->number)) |
| return -1; |
| |
| if (first) { |
| tmp = self->first; |
| Py_INCREF(first); |
| self->first = first; |
| Py_DECREF(tmp); |
| } |
| if (last) { |
| tmp = self->last; |
| Py_INCREF(last); |
| self->last = last; |
| Py_DECREF(tmp); |
| } |
| return 0; |
| } |
| |
| static PyMemberDef Custom_members[] = { |
| {"number", T_INT, offsetof(CustomObject, number), 0, |
| "custom number"}, |
| {NULL} |
| }; |
| |
| static PyObject * |
| Custom_getfirst(CustomObject *self, void *closure) |
| { |
| Py_INCREF(self->first); |
| return self->first; |
| } |
| |
| static int |
| Custom_setfirst(CustomObject *self, PyObject *value, void *closure) |
| { |
| PyObject *tmp; |
| if (value == NULL) { |
| PyErr_SetString(PyExc_TypeError, "Cannot delete the first attribute"); |
| return -1; |
| } |
| if (!PyUnicode_Check(value)) { |
| PyErr_SetString(PyExc_TypeError, |
| "The first attribute value must be a string"); |
| return -1; |
| } |
| tmp = self->first; |
| Py_INCREF(value); |
| self->first = value; |
| Py_DECREF(tmp); |
| return 0; |
| } |
| |
| static PyObject * |
| Custom_getlast(CustomObject *self, void *closure) |
| { |
| Py_INCREF(self->last); |
| return self->last; |
| } |
| |
| static int |
| Custom_setlast(CustomObject *self, PyObject *value, void *closure) |
| { |
| PyObject *tmp; |
| if (value == NULL) { |
| PyErr_SetString(PyExc_TypeError, "Cannot delete the last attribute"); |
| return -1; |
| } |
| if (!PyUnicode_Check(value)) { |
| PyErr_SetString(PyExc_TypeError, |
| "The last attribute value must be a string"); |
| return -1; |
| } |
| tmp = self->last; |
| Py_INCREF(value); |
| self->last = value; |
| Py_DECREF(tmp); |
| return 0; |
| } |
| |
| static PyGetSetDef Custom_getsetters[] = { |
| {"first", (getter) Custom_getfirst, (setter) Custom_setfirst, |
| "first name", NULL}, |
| {"last", (getter) Custom_getlast, (setter) Custom_setlast, |
| "last name", NULL}, |
| {NULL} |
| }; |
| |
| static PyObject * |
| Custom_name(CustomObject *self, PyObject *Py_UNUSED(ignored)) |
| { |
| return PyUnicode_FromFormat("%S %S", self->first, self->last); |
| } |
| |
| static PyMethodDef Custom_methods[] = { |
| {"name", (PyCFunction) Custom_name, METH_NOARGS, |
| "Return the name, combining the first and last name" |
| }, |
| {NULL} |
| }; |
| |
| static PyTypeObject CustomType = { |
| PyVarObject_HEAD_INIT(NULL, 0) |
| .tp_name = "custom3.Custom", |
| .tp_doc = "Custom objects", |
| .tp_basicsize = sizeof(CustomObject), |
| .tp_itemsize = 0, |
| .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, |
| .tp_new = Custom_new, |
| .tp_init = (initproc) Custom_init, |
| .tp_dealloc = (destructor) Custom_dealloc, |
| .tp_members = Custom_members, |
| .tp_methods = Custom_methods, |
| .tp_getset = Custom_getsetters, |
| }; |
| |
| static PyModuleDef custommodule = { |
| PyModuleDef_HEAD_INIT, |
| .m_name = "custom3", |
| .m_doc = "Example module that creates an extension type.", |
| .m_size = -1, |
| }; |
| |
| PyMODINIT_FUNC |
| PyInit_custom3(void) |
| { |
| PyObject *m; |
| if (PyType_Ready(&CustomType) < 0) |
| return NULL; |
| |
| m = PyModule_Create(&custommodule); |
| if (m == NULL) |
| return NULL; |
| |
| Py_INCREF(&CustomType); |
| PyModule_AddObject(m, "Custom", (PyObject *) &CustomType); |
| return m; |
| } |
| |
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 理解Rust引用及其生命周期标识(下)
· 从二进制到误差:逐行拆解C语言浮点运算中的4008175468544之谜
· .NET制作智能桌面机器人:结合BotSharp智能体框架开发语音交互
· 软件产品开发中常见的10个问题及处理方法
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
· 2025成都.NET开发者Connect圆满结束
· 后端思维之高并发处理方案
· 千万级大表的优化技巧
· 在 VS Code 中,一键安装 MCP Server!
· 10年+ .NET Coder 心语 ── 继承的思维:从思维模式到架构设计的深度解析