Gustavo Carneiro wrote:
Come on, I wouldn't say this interface is unreadable. It's stuff like this:
mod = Module('foo') Foo = CppClass('Foo', automatic_type_narrowing=True) mod.add_class(Foo) Foo.add_static_attribute(ReturnValue.new('int'), 'instance_count') Foo.add_constructor( CppConstructor([Parameter.new('std::string', 'datum')])) Foo.add_constructor(CppConstructor([])) Foo.add_method(CppMethod(ReturnValue.new('std::string'),
'get_datum', []))
Not that hard to write this, is it?
I didn't say it's unreadable and I didn't say it was hard to write. I'm just saying that you shouldn't have to write that at all if all you get is a straight mapping between a C(++) API and a Python API. That's what wrapper generators are there for: parse the interface description and generate the wrapper with as little human interaction as possible. And, that's what the wrapper languages (that you don't want to require people to learn) are good at.
For instance, I don't find this very readable:
static PyObject *__pyx_f_4spam_4Spam_get_amount(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds); /*proto*/
So far for the signature.
static PyObject *__pyx_f_4spam_4Spam_get_amount(PyObject *__pyx_v_self, PyObject *__pyx_args, PyObject *__pyx_kwds) { PyObject *__pyx_r; PyObject *__pyx_1 = 0; static char *__pyx_argnames[] = {0}; if (unlikely(!PyArg_ParseTupleAndKeywords(__pyx_args, __pyx_kwds, "", __pyx_argnames))) return 0; Py_INCREF(__pyx_v_self);
That's for argument parsing - no need to look at that.
/* "/Users/robert/sage/pyrex/cython-0.9.6.3/Demos/spam.pyx http://0.9.6.3/Demos/spam.pyx":16 *
- def get_amount(self):
return self.amount # <<<<<<<<<<<<<<
- def set_amount(self, new_amount): */
Here we go, this is the code that was translated into the C code below.
__pyx_1 = PyInt_FromLong(((struct __pyx_obj_4spam_Spam *)__pyx_v_self)->amount); if (unlikely(!__pyx_1)) {__pyx_filename = __pyx_f[0]; __pyx_lineno = 16; goto __pyx_L1;} __pyx_r = __pyx_1; __pyx_1 = 0; goto __pyx_L0;
The first line is so long to keep you from looking at it, so if you ignore the error handling, what you see is:
__pyx_1 = PyInt_FromLong(((struct __pyx_obj_4spam_Spam *)__pyx_v_self)->amount);
__pyx_r = __pyx_1;
Relatively straight assignment code, split into code for getting the value and code for setting the value.
__pyx_r = Py_None; Py_INCREF(Py_None); goto __pyx_L0; __pyx_L1:; Py_XDECREF(__pyx_1); __Pyx_AddTraceback("spam.Spam.get_amount "); __pyx_r = 0; __pyx_L0:; Py_DECREF(__pyx_v_self); return __pyx_r; }
Again, ignore the clean up code.
Admittedly, it's far from hand-optimised, aesthetic code, but it's definitely readable.
Compared with this code generated by pybindgen:
static PyObject * _wrap_fooinvoke_some_object_get_prefix() { PyObject *py_retval; std::string retval;
retval = invoke_some_object_get_prefix(); py_retval = Py_BuildValue("s#", retval.c_str(), retval.size()); return py_retval;
}
Ok, but that's almost exactly the code you wrote by hand, step by step through your Python API. So the question is: what's the advantage compared to writing the code *directly* by hand, i.e. in plain C?
Oh, and: you're not passing any arguments here. If you were, your function would look a bit more bloated, just like the Pyrex code.
Pybindgen generated code is almost as clean as the code you would write by hand. To me that counts a lot. I know some people don't care what happens underneath the tools they use as long as it works. I am not that kind of person.
I do care a lot, that's why I keep submitting patches to Cython. The better the code gets that Cython generates, the less I have to care about the generated C code when I write Cython code.
I think *that* is the essence of a code generator tool.
Stefan