Implement a PyTypeObject in C

Old C API

Truncated example of the PyUnicode_Type (Python str):

PyTypeObject PyUnicode_Type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "str",                        /* tp_name */
    sizeof(PyUnicodeObject),      /* tp_basicsize */
    0,                            /* tp_itemsize */
    /* Slots */
    (destructor)unicode_dealloc,  /* tp_dealloc */
    0,                            /* tp_print */
    0,                            /* tp_getattr */
    0,                            /* tp_setattr */
    0,                            /* tp_reserved */
    unicode_repr,                 /* tp_repr */
    &unicode_as_number,           /* tp_as_number */
    &unicode_as_sequence,         /* tp_as_sequence */
    &unicode_as_mapping,          /* tp_as_mapping */
    (hashfunc) unicode_hash,      /* tp_hash*/
    (...)
    0,                            /* tp_init */
    0,                            /* tp_alloc */
    unicode_new,                  /* tp_new */
    PyObject_Del,                 /* tp_free */
};

The type must then be initialized once by calling PyType_Ready():

if (PyType_Ready(&PyUnicode_Type) < 0) { /* handle the error */ }

This API has an obvious flaw: it rely on the exact implementation of PyTypeObject, the developer has to know all fields.

Variant using C99 syntax:

static PyTypeObject _abc_data_type = {
    PyVarObject_HEAD_INIT(&PyType_Type, 0)
    "_abc_data",                        /*tp_name*/
    sizeof(_abc_data),                  /*tp_size*/
    .tp_dealloc = (destructor)abc_data_dealloc,
    .tp_flags = Py_TPFLAGS_DEFAULT,
    .tp_alloc = PyType_GenericAlloc,
    .tp_new = abc_data_new,
};

PyType_FromSpec()

Python 3.1 introduced a new function:

PyObject* PyType_FromSpec(PyType_Spec *spec)

Documentation:

Creates and returns a heap type object from the spec passed to the function.

There are two additional private functions (excluded from the Stable ABI):

PyObject* PyType_FromSpecWithBases(PyType_Spec*, PyObject*);
void* PyType_GetSlot(PyTypeObject*, int);

PyType_GetSlot() expects a slot number which comes from Include/typeslots.inc: see slotoffsets array. The file contains the warning:

Do not renumber the file; these numbers are part of the stable ABI.

Some slots have been disabled (bpo-10181):

/* Disabled, see #10181 */
#undef Py_bf_getbuffer
#undef Py_bf_releasebuffer

Examples of slots:

#define Py_nb_add 7

#define Py_tp_alloc 47

#define Py_tp_call 50

#define Py_tp_clear 51

#define Py_tp_doc 56

#define Py_tp_getattr 57

Example of type:

static PyType_Slot PyCursesPanel_Type_slots[] = {
    {Py_tp_dealloc, PyCursesPanel_Dealloc},
    {Py_tp_methods, PyCursesPanel_Methods},
    {0, 0},
};

static PyType_Spec PyCursesPanel_Type_spec = {
    "_curses_panel.panel",
    sizeof(PyCursesPanelObject),
    0,
    Py_TPFLAGS_DEFAULT,
    PyCursesPanel_Type_slots
};

Later initialized by:

PyObject *v = PyType_FromSpec(&PyCursesPanel_Type_spec);
if (v == NULL)
    goto fail;
((PyTypeObject *)v)->tp_new = NULL;
_curses_panelstate(m)->PyCursesPanel_Type = v;

Missing fields:

  • To provide a __dict__ in a defined C type, tp_dict_offset slot must be set, but this slot is missing from the stable ABI.

  • Same issue with tp_weaklistoffset

Remove cross-version binary compatibility

See bpo-32388.

PyStructSequence_InitType()

There are two public APIs for structure sequences that are wrappers around PyType_Ready() for initializing a PyTypeObject in-place:

  • PyStructSequence_InitType()

  • PyStructSequence_InitType2()

A third-party developer should prefer PyStructSequence_NewType() to create a type object from an array of PyStructSequence_Desc.