Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Define Python class from C

I wrapped some C code for Python and it works. The C module creates a handle, which I pass to Python as PyCapsule. The API I would like to have can be made in Python like:

import wrapped

class Test(object):
   def __init__(self, loc ):
      self.handle = wrapped.new(loc)

   def foo(self, data):
      return wrapped.foo(self.handle, data)

So the question is more a cosmetic issue. Do I have to wrap the wrapper, or can I move code like shown above into the C code, i.e. export a class instead of a bunch of functions?

like image 763
Lars Hanke Avatar asked Feb 03 '15 09:02

Lars Hanke


People also ask

How do you define a class Python?

A class in Python can be defined using the class keyword. As per the syntax above, a class is defined using the class keyword followed by the class name and : operator after the class name, which allows you to continue in the next indented line to define class members.

Why define classes in Python?

Classes provide a means of bundling data and functionality together. Creating a new class creates a new type of object, allowing new instances of that type to be made. Each class instance can have attributes attached to it for maintaining its state.

What is PyObject in C?

A PyObject is in fact just a Python object at the C level. And since integers in Python are objects, they are also PyObject s. It doesn't matter whether it was written in Python or in C, it is a PyObject at the C level regardless.


1 Answers

Yes, you can create your own class types in C. From the C API a Python type/class is an instance of the PyTypeObject structure filled in appropriately for your type. The whole procedure for doing this is outlined nicely in the following tutorial:

https://docs.python.org/2/extending/newtypes.html

This will walk you through defining the initial core type and then adding data and methods to the type/class. At first it may seem like an awful lot of work just to get a class implemented in C, but once you do it a few times and get comfortable with it, it's really not so bad.

Here is a bare bones implementation of the Test class you define in your question.

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

typedef struct {
    PyObject_HEAD
    /* Your internal 'loc' data. */
    int loc;
} Test;

static void
MyTest_dealloc(Test* self)
{
    self->ob_type->tp_free((PyObject*)self);
}

static PyObject *
Test_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    Test *self;

    self = (Test *)type->tp_alloc(type, 0);
    self->loc = 0;

    return (PyObject *)self;
}

static int
Test_init(Test *self, PyObject *args, PyObject *kwds)
{
    if (! PyArg_ParseTuple(args, "i", &self->loc))
        return -1;

    return 0;
}

static PyMemberDef Test_members[] = {
    {"loc", T_INT, offsetof(Test, loc), 0, "mytestobj loc"},
    {NULL}  /* Sentinel */
};

static PyObject *
Test_foo(Test* self, PyObject *args)
{
    int data;
    PyObject *result;

    if (! PyArg_ParseTuple(args, "i", &data)) {
        return NULL;
    }

    /* We'll just return data + loc as our result. */
    result = Py_BuildValue("i", data + self->loc);

    return result;
}
static PyMethodDef Test_methods[] = {
    {"foo", (PyCFunction)Test_foo, METH_VARARGS,
     "Return input parameter added to 'loc' argument from init.",
    },
    {NULL}  /* Sentinel */
};

static PyTypeObject mytest_MyTestType = {
    PyObject_HEAD_INIT(NULL)
    0,                         /*ob_size*/
    "mytest.MyTest",             /*tp_name*/
    sizeof(Test), /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    (destructor)MyTest_dealloc,/*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*/
    "MyTest objects",          /* tp_doc */
    0,                         /* tp_traverse */
    0,                         /* tp_clear */
    0,                         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    Test_methods,      /* tp_methods */
    Test_members,      /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)Test_init,/* tp_init */
    0,                         /* tp_alloc */
    Test_new,                 /* tp_new */
};

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

#ifndef PyMODINIT_FUNC  /* declarations for DLL import/export */
#define PyMODINIT_FUNC void
#endif
PyMODINIT_FUNC
initmytest(void)
{
    PyObject* m;

    if (PyType_Ready(&mytest_MyTestType) < 0)
        return;

    m = Py_InitModule3("mytest", mytest_methods,
                       "Example module that creates an extension type.");

    Py_INCREF(&mytest_MyTestType);
    PyModule_AddObject(m, "Test", (PyObject *)&mytest_MyTestType);
}

And its usage from the Python interpreter:

>>> from mytest import Test
>>> t = Test(5)
>>> t.foo(10)
15
like image 94
mshildt Avatar answered Sep 30 '22 17:09

mshildt