Search code examples
pythonc++cpython-c-api

How to define constant values of Python types defined in C++ extension with Py_LIMITED_API?


I want to use Py_LIMITED_API. Thus, instead of defining my Python type statically, I define it dynamically, with specs and slots as follows.

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;
}

With this definition of my module, I can write the following.

from MyModule import *
print( MyType.Normal )
print( MyType.Custom )

My problem is that with Py_LIMITED_API, I cannot use tp_dict to define the constant values Normal and Custom.

I've tried to use PyType_GetSlot, but Py_tp_dict does not exist. (I guess I understand why: it's because it would allow modifications of existing types.)

Now my question is: In the code above with specs and slots, how can I define the constants Normal and Custom of the type MyType without using tp_dict?


Solution

  • There's a very simple answer: call PyObject_SetAttrString on the type object.

    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;
        PyObject_SetAttrString( (PyObject*)MyTypePtr, "Normal", PyLong_FromLong( 0 ) );
        PyObject_SetAttrString( (PyObject*)MyTypePtr, "Custom", PyLong_FromLong( 15 ) );
        Py_INCREF( MyTypePtr );
        PyModule_AddObject( m, "MyType", (PyObject *)MyTypePtr );
        return m;
    }
    

    Guess it also works with PyObject_SetAttr.

    No more use of tp_dict.

    Can enable Py_LIMITED_API with this.