2

Using the Python C/API, how do I create a normal Python class using the normal Python class-creation mechanism (i.e.: not an extension type)?

In other words, what is the Python C/API equivalent (in the sense that it does exactly the same in all cases) of a statement

class X(bases):
    ...some methods/attributes here...

2 Answers2

4

I'm not sure what you mean by "the normal Python class-creation mechanism", but...

There's a documentation page dedicated to this: https://docs.python.org/3/extending/newtypes.html -- it creates a new type in an extension module, which is equivalent to creating a new class in Python code.

The minimal example presented there is:

#include <Python.h>

typedef struct {
    PyObject_HEAD
    /* Type-specific fields go here. */
} noddy_NoddyObject;

static PyTypeObject noddy_NoddyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "noddy.Noddy",             /* tp_name */
    sizeof(noddy_NoddyObject), /* tp_basicsize */
    0,                         /* tp_itemsize */
    0,                         /* tp_dealloc */
    0,                         /* tp_print */
    0,                         /* tp_getattr */
    0,                         /* tp_setattr */
    0,                         /* tp_reserved */
    0,                         /* tp_repr */
    0,                         /* tp_as_number */
    0,                         /* tp_as_sequence */
    0,                         /* tp_as_mapping */
    0,                         /* tp_hash  */
    0,                         /* tp_call */
    0,                         /* tp_str */
    0,                         /* tp_getattro */
    0,                         /* tp_setattro */
    0,                         /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT,        /* tp_flags */
    "Noddy objects",           /* tp_doc */
};

static PyModuleDef noddymodule = {
    PyModuleDef_HEAD_INIT,
    "noddy",
    "Example module that creates an extension type.",
    -1,
    NULL, NULL, NULL, NULL, NULL
};

PyMODINIT_FUNC
PyInit_noddy(void)
{
    PyObject* m;

    noddy_NoddyType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&noddy_NoddyType) < 0)
        return NULL;

    m = PyModule_Create(&noddymodule);
    if (m == NULL)
        return NULL;

    Py_INCREF(&noddy_NoddyType);
    PyModule_AddObject(m, "Noddy", (PyObject *)&noddy_NoddyType);
    return m;
}
Eli Bendersky
  • 263,248
  • 89
  • 350
  • 412
  • This is exactly what I do NOT want. I do not want an extension type, but just a Python class created at runtime. – Jeroen Demeyer Oct 11 '16 at 13:44
  • @JeroenDemeyer: Your questions says "Python C/API equivalent of a statement like", so this is what I understood. Please consider rephrasing your question to be clearer. – Eli Bendersky Oct 11 '16 at 19:10
  • I mean "equivalent" in the most literal sense. I don't mean "similar", I really mean "exactly the same". – Jeroen Demeyer Oct 12 '16 at 15:20
4

In Python you can get programmatically create a class by calling the type built in function. See this answer for example.

This takes three arguments: a name, a tuple of bases, and a dictionary.

You can get the Python type in the C api as PyType_Type. You then just need to call it using one of the standard methods for calling PyObject* callables:

// make a tuple of your bases
PyObject* bases = PyTuple_Pack(0); // assume no bases
// make a dictionary of member functions, etc
PyObject* dict = PyDict_New(); // empty for the sake of example 
PyObject* my_new_class = PyObject_CallFunction(&PyType_Type,"sOO",
                                             "X", // class name
                                             bases,
                                             dict);
// check if null

// decref bases and dict
Py_CLEAR(bases);
Py_CLEAR(dict);

(Note that you have to do &PyType_Type - the documentation implies that it's a PyObject* but it isn't!)

Community
  • 1
  • 1
DavidW
  • 29,336
  • 6
  • 55
  • 86
  • Thanks, that seems to work. Although I wonder if "class Derived(Base): pass" is really 100% equivalent to "Derived = type("Derived", (Base,), {})" if metaclasses are involved. – Jeroen Demeyer Oct 11 '16 at 13:47
  • I believe that if metaclasses are involved you should call `Derived = metaclass("Derived", (Base,), {})`. However, this is slightly beyond what I'm actually sure of... – DavidW Oct 11 '16 at 14:30
  • Just as an extra thing - have you seen this https://mail.python.org/pipermail/python-dev/2009-July/090921.html? There does look to be a second way of doing it involving allocating `PyTypeObject`s yourself (but no-one seems to like it...) – DavidW Oct 11 '16 at 14:33