PyInline Released: Put C source code directly "inline" with your Python!

Thomas Heller thomas.heller at ion-tof.com
Wed Aug 29 03:00:41 EDT 2001


Inspired by the recent discussion of perls inline
module I wrote the attached module, which is yet
another way to do it.

Enjoy,

Thomas

----inline.py---
class C_Func:
    def __init__(self, inline, name, code, doc, prefix):
        self.name = name
        self.code = code
        self.doc = doc
        self.module = inline
        self.prefix = prefix

    def create(self, file):
        file.write("static  PyObject *%s(PyObject *self, PyObject *args)\n"\
                   % (self.prefix + self.name))
        file.write(self.code)
        file.write("\n");

    def __call__(self, *args, **kw):
        mod = self.module.create()
        func = getattr(mod, self.name)
        return func(*args, **kw)

class Inline:
    def __init__(self, name):
        self.name = name
        self.functions = {}
        self.mod = None
        self.libraries = []

    def c_func(self, name, code, doc=None, prefix=''):
        if self.mod:
            raise RuntimeError, \
                  "Cannot add any more functions: Extension already created"
        func = C_Func(self, name, code, doc, prefix)
        self.functions[name] = func
        return func

    def create(self):
        if not self.mod:
            # generate the code
            import StringIO
            buffer = StringIO.StringIO()
            buffer.write("#include <python.h>\n")
            buffer.write("\n")

            for func in self.functions.values():
                func.create(buffer)

            buffer.write("static PyMethodDef methods[] = {\n")
            for fname in self.functions.keys():
                pre = self.functions[fname].prefix
                buffer.write('    { "%s", %s, METH_VARARGS },\n' % \
                           (fname, pre + fname))
            buffer.write('    { NULL }, /* sentinel */\n};\n')
            buffer.write("\n")

            buffer.write('void init%s(void)\n' % self.name)
            buffer.write('{\n    Py_InitModule3("%s", methods, "");\n}\n' % \
                         self.name)

            code = buffer.getvalue()
            buffer.close()

            # calulate md5 digest
            import md5
            m = md5.new()
            m.update(code)
            hd = m.hexdigest()

            # compare to existing digest, if present
            try:
                old_md5 = open(self.name + '.md5', 'r').read()
            except:
                old_md5 = None

            # if the digests match, no recompilation is needed
            if old_md5 != hd:
                # recompilation needed
                file = open(self.name + '.c', 'w')
                file.write(code)
                file.close()

                from distutils.core import setup, Extension

                ext = Extension(self.name, sources=[self.name + '.c'],
                                libraries=self.libraries)

                setup(ext_modules=[ext], script_name="dummy",
                      script_args=["-q", "install", "--install-platlib=."])

                open(self.name + '.md5', 'w').write(hd)
                import os
                os.remove(self.name + '.c')

            # import module
            self.mod = __import__(self.name)

    def __getattr__(self, name):
        if name not in self.functions.keys():
            raise AttributeError, name
        if not self.mod:
            self.create()
        return getattr(self.mod, name)

if __name__ == '__main__':
    module = Inline("_sample")

    # define a function implemented in C
    # The function declaration will be added automatically,
    # in this case 'static PyObject *fib_c(PyObject *self, PyObject *args)'
    module.c_func("fib_c", r"""
{
    int i;
    static int _fib(int);
    if (!PyArg_ParseTuple(args, "i", &i))
        return NULL;
    return Py_BuildValue("i", _fib(i));
}

static int _fib(int i)
{
    if (i <= 2)
        return i;
    return _fib(i - 1) + _fib(i - 2);
}
    """)


    # define a function implemented in C
    # The function declaration will be added automatically,
    # in this case 'static PyObject *fib_c(PyObject *self, PyObject *args)'
    module.c_func("fact_c", r"""
{
    int i;
    PyObject *inst;
    static int _fact(int);
    if (!PyArg_ParseTuple(args, "Oi", &inst, &i))
        return NULL;
    return Py_BuildValue("i", _fact(i));
}

static int _fact(int i)
{
    if (i <= 1)
        return i;
    return i * _fact(i - 1);
}
    """)



    # generate and compile an extension module (if needed),
    # retrieve the function from it.
    fib_c = module.fib_c


    # a similar function implemented in Python
    def fib_py(i):
        if i <= 2:
            return i
        return fib_py(i-1) + fib_py(i-2)

    # we can also define a class, and use the extension module's functions
    # as instance methods
    class X:
        def fact_py(self, i):
            if i <= 1:
                return i
            return i * self.fact_py(i-1)

    # convert the extension function into a unbound method
    # and attach it to the class
    import new
    X.fact_c = new.instancemethod(module.fact_c, None, X)

    # do a little benchmark
    import time

    start = time.clock()
    print "fib_py(30) =", fib_py(30), "%s seconds" % (time.clock() - start)

    start = time.clock()
    print "fib_c(30) =", fib_c(30), "%s seconds" % (time.clock() - start)

    x = X()

    start = time.clock()
    print "x.fact_py(12) =", x.fact_py(12), "%s seconds" % (time.clock() - start)

    start = time.clock()
    print "x.fact_c(12) =", x.fact_c(12), "%s seconds" % (time.clock() - start)






More information about the Python-list mailing list