You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 00:54:51