Here’s a Python 3.12+ extension module mymod
that defines a type MyStr
with settable attributes number
(int) and flag
(bool), and a get_data
method, without using PyUnicodeObject
et. al.:
#define Py_LIMITED_API 0x030c0000 /* 3.12 */
#include <Python.h>
#include <stddef.h>
typedef struct {
int number;
char flag;
} MyStr_Data;
static PyTypeObject *MyStr_Type = NULL;
static MyStr_Data *
mystr_get_data(PyObject *self) {
return PyObject_GetTypeData(self, MyStr_Type);
}
static PyObject *
mystr_get_data_meth(PyObject *self, PyObject *dummy)
{
MyStr_Data *data = mystr_get_data(self);
if (!data) {
return NULL;
}
return Py_BuildValue("nl", data->number, data->flag);
}
static PyType_Spec mystr_spec = {
.name = "mymod.MyStr",
.basicsize = (int)-sizeof(MyStr_Data),
.slots = (PyType_Slot[]) {
{Py_tp_members, (PyMemberDef[]) {
{"number", Py_T_INT, offsetof(MyStr_Data, number),
Py_RELATIVE_OFFSET},
{"flag", Py_T_BOOL, offsetof(MyStr_Data, flag),
Py_RELATIVE_OFFSET},
{0} /* sentinel */
}},
{Py_tp_methods, (PyMethodDef[]){
{"get_data", mystr_get_data_meth, METH_NOARGS,
"get the extra data as a tuple"},
{0} /* sentinel */
}},
{0} /* sentinel */
}
};
int
mymod_exec(PyObject *mod)
{
if (MyStr_Type) {
PyErr_SetString(PyExc_ImportError,
"cannot load module more than once per process");
return -1;
}
MyStr_Type = (PyTypeObject *)PyType_FromSpecWithBases(
&mystr_spec, (PyObject *)&PyUnicode_Type);
if (!MyStr_Type) {
return -1;
}
if (PyModule_AddType(mod, MyStr_Type) < 0) {
Py_DECREF(MyStr_Type);
return -1;
}
Py_DECREF(MyStr_Type);
return 0;
}
static PyModuleDef mymod_def = {
.m_name = "mymod",
.m_slots = (PyModuleDef_Slot[]) {
{Py_mod_exec, mymod_exec},
{0} /* sentinel */
},
};
PyObject *
PyInit_mymod(void) {
return PyModuleDef_Init(&mymod_def);
}
This of course ignores a lot of complications, but, most are orthogonal.
AFAICS, PyObjC would want a bit of new API: PyUnicode_NewSubtype
, a function like PyUnicode_New
that additionally takes a “type” argument, and returns an uninitialized string that’s “fillable” with the same caveats as PyUnicode_New
.
I have a draft (!) branch adding that, with a test type that exercises some of the complications.
I didn’t get PyObjC tests to run yet, I didn’t try porting NumPy, I didn’t audit the edge cases, but so far this seems viable.
(Subtype support in PyUnicode_Writer
would also be nice, later.)
Do you have an example of one that doesn’t?