如何在Py_LIMITED_API下为C++扩展的Python类型定义常量
问题
我希望使用Py_LIMITED_API,因此未静态定义Python类型,而是通过specs和slots动态定义了MyType。原本通过PyDict_SetItemString修改tp_dict,可以在Python中通过MyType.Normal、MyType.Custom访问常量,但在Py_LIMITED_API下无法直接使用tp_dict,尝试用PyType_GetSlot获取tp_dict也失败。
相关C++实现代码如下:
struct MyType { PyObject_HEAD }; static void MyType_dealloc( MyType * self ) { PyObject_DEL( self ); } static PyObject * MyType_new( PyTypeObject * type, PyObject * args, PyObject * kwds ) { return (PyObject *)PyObject_NEW( MyType, type ); } static int MyType_init( MyType * self, PyObject * args, PyObject * kwds ) { return 0; } static PyType_Slot MyType_slots[] = { { Py_tp_doc, (void *)PyDoc_STR("MyType objects") }, { Py_tp_dealloc, (void *)&MyType_dealloc }, { Py_tp_init, (void *)&MyType_init }, { Py_tp_new, (void *)&MyType_new }, { 0, NULL } }; static PyType_Spec MyType_spec = { "MyType", sizeof(MyType), 0, Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, MyType_slots }; static PyTypeObject * MyTypePtr = NULL; static PyModuleDef MyModule = { PyModuleDef_HEAD_INIT, "MyModule", "My C++ Module.", -1, NULL, NULL, NULL, NULL, NULL }; PyMODINIT_FUNC PyInit_MyModule(void) { PyObject* m = PyModule_Create( &MyModule ); if (m == NULL) return NULL; MyTypePtr = (PyTypeObject*)PyType_FromSpec( &MyType_spec ); if ( MyTypePtr == NULL ) return NULL; if ( PyType_Ready( MyTypePtr ) < 0 ) return NULL; PyDict_SetItemString( MyTypePtr->tp_dict, "Normal", PyLong_FromLong( 0 ) ); PyDict_SetItemString( MyTypePtr->tp_dict, "Custom", PyLong_FromLong( 15 ) ); Py_INCREF( MyTypePtr ); PyModule_AddObject( m, "MyType", (PyObject *)MyTypePtr ); return m; }
请问:如何在不使用tp_dict的前提下,为该动态定义的MyType类型添加Normal和Custom常量?
解决方案
可以使用Py_SetAttrString函数替代PyDict_SetItemString来添加类属性,该函数属于Py_LIMITED_API支持的公开接口,不需要直接访问类型对象的内部tp_dict。
修改PyInit_MyModule中的对应代码即可,替换后的初始化函数如下:
PyMODINIT_FUNC PyInit_MyModule(void) { PyObject* m = PyModule_Create( &MyModule ); if (m == NULL) return NULL; MyTypePtr = (PyTypeObject*)PyType_FromSpec( &MyType_spec ); if ( MyTypePtr == NULL ) return NULL; if ( PyType_Ready( MyTypePtr ) < 0 ) return NULL; // 替换原有的PyDict_SetItemString调用 PyObject* normal_val = PyLong_FromLong(0); if (!normal_val || Py_SetAttrString((PyObject*)MyTypePtr, "Normal", normal_val) < 0) { Py_XDECREF(normal_val); Py_DECREF(m); return NULL; } Py_DECREF(normal_val); // Py_SetAttrString会增加引用,这里释放我们创建的对象 PyObject* custom_val = PyLong_FromLong(15); if (!custom_val || Py_SetAttrString((PyObject*)MyTypePtr, "Custom", custom_val) < 0) { Py_XDECREF(custom_val); Py_DECREF(m); return NULL; } Py_DECREF(custom_val); Py_INCREF( MyTypePtr ); PyModule_AddObject( m, "MyType", (PyObject *)MyTypePtr ); return m; }
说明:
Py_SetAttrString通过Python的标准属性设置机制为类型对象添加属性,完全符合Py_LIMITED_API的约束,不会依赖内部结构体的成员。- 注意引用计数管理:
PyLong_FromLong创建的对象在被Py_SetAttrString使用后,需要手动调用Py_DECREF释放,因为Py_SetAttrString会对传入的属性值执行一次Py_INCREF,确保对象不会被提前销毁。 - 添加了错误检查逻辑,避免内存泄漏或异常传播。
内容的提问来源于stack exchange,提问作者Didier Trosset
相关产品推荐
相关产品推荐

