Python C API cheatsheet

PyObject header

// ref: python source code
// Python/Include/object.c
#define _PyObject_HEAD_EXTRA \
    struct _object *_ob_next;\
    struct _object *_ob_prev;

#define PyObject_HEAD    \
    _PyObject_HEAD_EXTRA \
    Py_ssize_t ob_refcnt;\
    struct _typeobject *ob_type;

Python C API Template

C API source

#include <Python.h>

typedef struct {
    PyObject_HEAD
} spamObj;

static PyTypeObject spamType = {
    PyObject_HEAD_INIT(&PyType_Type)
    0,                  //ob_size
    "spam.Spam",        //tp_name
    sizeof(spamObj),    //tp_basicsize
    0,                  //tp_itemsize
    0,                  //tp_dealloc
    0,                  //tp_print
    0,                  //tp_getattr
    0,                  //tp_setattr
    0,                  //tp_compare
    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
    "spam objects",     //tp_doc
};

static PyMethodDef spam_methods[] = {
    {NULL}  /* Sentinel */
};

/* declarations for DLL import */
#ifndef PyMODINIT_FUNC
#define PyMODINIT_FUNC void
#endif

PyMODINIT_FUNC
initspam(void)
{
    PyObject *m;
    spamType.tp_new = PyType_GenericNew;
    if (PyType_Ready(&spamType) < 0) {
        goto END;
    }
    m = Py_InitModule3("spam", spam_methods, "Example of Module");
    Py_INCREF(&spamType);
    PyModule_AddObject(m, "spam", (PyObject *)&spamType);
END:
    return;
}

Prepare setup.py

from distutils.core import setup
from distutils.core import Extension

setup(name="spam",
      version="1.0",
      ext_modules=[Extension("spam", ["spam.c"])])

Build C API source

$ python setup.py build
$ python setup.py install

Run the C module

>>> import spam
>>> spam.__doc__
'Example of Module'
>>> spam.spam
<type 'spam.Spam'>

PyObject with Member and Methods

C API source

#include <Python.h>
#include <structmember.h>

typedef struct {
    PyObject_HEAD
    PyObject *hello;
    PyObject *world;
    int spam_id;
} spamObj;

static void
spamdealloc(spamObj *self)
{
    Py_XDECREF(self->hello);
    Py_XDECREF(self->world);
    self->ob_type
        ->tp_free((PyObject*)self);
}

/* __new__ */
static PyObject *
spamNew(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    spamObj *self = NULL;

    self = (spamObj *)
           type->tp_alloc(type, 0);
    if (self == NULL) {
        goto END;
    }
    /* alloc str to hello */
    self->hello =
        PyString_FromString("");
    if (self->hello == NULL)
    {
        Py_XDECREF(self);
        self = NULL;
        goto END;
    }
    /* alloc str to world */
    self->world =
        PyString_FromString("");
    if (self->world == NULL)
    {
        Py_XDECREF(self);
        self = NULL;
        goto END;
    }
    self->spam_id = 0;
END:
    return (PyObject *)self;
}

/* __init__ */
static int
spamInit(spamObj *self, PyObject *args, PyObject *kwds)
{
    int ret = -1;
    PyObject *hello=NULL,
             *world=NULL,
             *tmp=NULL;

    static char *kwlist[] = {
        "hello",
        "world",
        "spam_id", NULL};

    /* parse input arguments */
    if (! PyArg_ParseTupleAndKeywords(
          args, kwds,
          "|OOi",
          kwlist,
          &hello, &world,
          &self->spam_id)) {
        goto END;
    }
    /* set attr hello */
    if (hello) {
        tmp = self->hello;
        Py_INCREF(hello);
        self->hello = hello;
        Py_XDECREF(tmp);
    }
    /* set attr world */
    if (world) {
        tmp = self->world;
        Py_INCREF(world);
        self->world = world;
        Py_XDECREF(tmp);
    }
    ret = 0;
END:
    return ret;
}

static long
fib(long n) {
    if (n<=2) {
        return 1;
    }
    return fib(n-1)+fib(n-2);
}

static PyObject *
spamFib(spamObj *self, PyObject *args)
{
    PyObject  *ret = NULL;
    long arg = 0;

    if (!PyArg_ParseTuple(args, "i", &arg)) {
        goto END;
    }
    ret = PyInt_FromLong(fib(arg));
END:
    return ret;
}

//ref: python doc
static PyMemberDef spam_members[] = {
    /* spameObj.hello*/
    {"hello",                   //name
     T_OBJECT_EX,               //type
     offsetof(spamObj, hello),  //offset
     0,                         //flags
     "spam hello"},             //doc
    /* spamObj.world*/
    {"world",
     T_OBJECT_EX,
     offsetof(spamObj, world),
     0,
     "spam world"},
    /* spamObj.spam_id*/
    {"spam_id",
     T_INT,
     offsetof(spamObj, spam_id),
     0,
     "spam id"},
    /* Sentiel */
    {NULL}
};

static PyMethodDef spam_methods[] = {
    /* fib */
    {"spam_fib",
     (PyCFunction)spamFib,
     METH_VARARGS,
     "Calculate fib number"},
    /* Sentiel */
    {NULL}
};

static PyMethodDef module_methods[] = {
    {NULL}  /* Sentinel */
};

static PyTypeObject spamKlass = {
    PyObject_HEAD_INIT(NULL)
    0,                               //ob_size
    "spam.spamKlass",                //tp_name
    sizeof(spamObj),                 //tp_basicsize
    0,                               //tp_itemsize
    (destructor) spamdealloc,        //tp_dealloc
    0,                               //tp_print
    0,                               //tp_getattr
    0,                               //tp_setattr
    0,                               //tp_compare
    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 |
    Py_TPFLAGS_BASETYPE,             //tp_flags
    "spamKlass objects",             //tp_doc
    0,                               //tp_traverse
    0,                               //tp_clear
    0,                               //tp_richcompare
    0,                               //tp_weaklistoffset
    0,                               //tp_iter
    0,                               //tp_iternext
    spam_methods,                    //tp_methods
    spam_members,                    //tp_members
    0,                               //tp_getset
    0,                               //tp_base
    0,                               //tp_dict
    0,                               //tp_descr_get
    0,                               //tp_descr_set
    0,                               //tp_dictoffset
    (initproc)spamInit,              //tp_init
    0,                               //tp_alloc
    spamNew,                         //tp_new
};

/* declarations for DLL import */
#ifndef PyMODINIT_FUNC
#define PyMODINIT_FUNC void
#endif

PyMODINIT_FUNC
initspam(void)
{
    PyObject* m;

    if (PyType_Ready(&spamKlass) < 0) {
        goto END;
    }

    m = Py_InitModule3(
      "spam",         // Mod name
      module_methods, // Mod methods
      "Spam Module"); // Mod doc

    if (m == NULL) {
        goto END;
    }
    Py_INCREF(&spamKlass);
    PyModule_AddObject(
      m,                           // Module
      "SpamKlass",                 // Class Name
      (PyObject *) &spamKlass);    // Class
END:
    return;
}

Compare performance with pure Python

>>> import spam
>>> o = spam.SpamKlass()
>>> def profile(func):
...   def wrapper(*args, **kwargs):
...     s = time.time()
...     ret = func(*args, **kwargs)
...     e = time.time()
...     print e-s
...   return wrapper
...
>>> def fib(n):
...   if n <= 2:
...     return n
...   return fib(n-1)+fib(n-2)
...
>>> @profile
... def cfib(n):
...   o.spam_fib(n)
...
>>> @profile
... def pyfib(n):
...   fib(n)
...
>>> cfib(30)
0.0106310844421
>>> pyfib(30)
0.399799108505