True Inheritable Classes In C (was Generic Interface)

Dianne Hackborn (hackbod@saurian.CS.ORST.EDU)
2 Mar 1995 08:33:32 GMT

Hark! The herald lutz@humpback.KaPRE.COM (Mark Lutz) posts:
| Some more thoughts on your proposal: maybe there should also be
| some standardized interface to object-specific methods.

Ah-ha... a part of what I was just about to post on.

Is there currently any way to create an object in C, which is an actual
python "Class" object and can be used as a superclass in new Python [and C]
objects? The application I am wanting to use this for is in a GUI -- I
need to make a base "Widget" class in C, which can be subclasses in Python
to do special interface handling and creating specialized classes.
Basically the standard OO approach to GUIs.

Some of the issues I am looking at:

- I need some way to easily construct a "Class" object, with the
appropriate function/member objects created from the C functions I
supply, etc.

- I need to be able to make sure -any- calls I make on my Widget class --
both internally and from other parts of my application -- go through
the complete Class interface so that sub-classes can overide my basic
functions.

Those two look like basically no-brainer coding things. The issues I am
still working on:

- I need to figure out how to handle instantiation. The C base class(es)
must be able to allocate their own private memory for each instance and
be able to get at it when any of their member functions are called.
My first thought was that their member functions would be supplied
another parameter like "self" -- say "priv_self" -- when they are called,
which would be determined by the standard Class functions.

- How should this be integrated with the current language implementation?
As far as I can tell, I would either need to mimic the current
ClassObject implementation [my new stuff still has to be a
ClassObjectType], which seems... ugly. So what probably would need to
be done would be to expand the current Class implementation, right?

- How much overhead am I talking about? Calling member functions which
just end up going down to the C class functions seems like it would be a
fair amount, under the current implementation of doing a getattr,
creating a function object, returning it, calling it. Might the expanded
Class be able to short-cut this and call the C member functions directly?
Hmmmm... actually, I suppose the Class would have to create all its
function/member objects when the class is created/instantiated, so this
might not be such an issue.

At any rate, is this something that anyone else could use? Would it fit in
well with Python? Unless there is some other way to do this that I have
overlooked, it is something I am going to either need to find a current
implementation of to use, or write one myself. And I -really- need it as
soon as possible. :}

[Maybe this is already in Python 1.2...?? ;) ;)]

| For instance, the list 'append' method is unique to lists, and
| not immediately available through the proposed interface. I could
| simulate 'append' by 'setslice', but a standard way to get at such
| object methods would be simpler. Something like:
|
| PyObject* PyObject_CallMethod(object, method-name, args...)
|
| which would just call the object's tp_getattr method, and
| call the resulting methodobject; like "object.method(args)".
| So if I had a list, I could just:
|
| newlist = PyObject_CallMethod(oldlist, "append", item)

See below. :)

| - 'Call' functions that allows arguments to be passed in as a
| varargs list (rather than requiring a tuple be built).

See below, kind of. :)

| - Ideally, 'Call' ops also provide conversion from C->Python
| automatically, via a mkvalue format list. This gets close to
| the embedded-call API ideas, but here the function is a python
| object or an object's method, not a user-defined item.

See below. :)

| - A more complete definition. For instance, there's no easy
| way to build a tuple; above, I built a list, set item 0,
| and then convered to a tuple. But Tuple_New seems useless:
|
| PyObject *args = PyTuple_New(1);
| <how to set a tuple's item here: sq_ass_item not defined?>
|
| Of course, I could revert to the tuple's C functions, but
| this defeats the purpose. These functions aren't tuple
| 'methods', so there's no way to get to them as object Attr's.

I'm not sure what you mean by this, as far as calling objects; it's fine
just building a standard tuple for that, isn't it? At any rate, here are a
couple helper routines I quickly wrote up a little while ago. A couple
object calling functions like you mentioned above, plus some routines for
getting and setting attributes which have functions associated with them,
rather than just stuffing into structure elements. I hope they are fairly
self-explanatory.

**NOTE** I don't even know if I have used the vararg call function yet, so
this code should be assumed to contain bugs. But if anyone wants to make
it correct/better, I sure wouldn't mind. :) And glancing at the varag
function, it looks like it is not dereferencing the argument tuple, as it
should be... <sigh>

Oh, and honest to ghod, this is almost verbatim from the code I originally
wrote; the function names have not been changed in any way, shape, or
form... ;) Oh, and the D() stuff is just a debugging macro.

----tools.h------------------>8-----SNIP!----->8-----------------------------
#ifndef INTRpytools_H
#define INTRpytools_H

#include (python includes)

typedef PyObject* (*PyGetMember) FPROTO((PyObject* self,
char* name, void* extdata));
typedef int (*PySetMember) FPROTO((PyObject* self, char* name,
PyObject* val, void* extdata));

typedef struct actmemberlist {
char* name;
PyGetMember getfunc;
PySetMember setfunc;
int readonly;
void* extdata;
} PyActMemberDef;

PyObject* PyObject_CallMethodA (PyObject* ob, char* name, PyObject* args);
PyObject* PyObject_CallMethod ();
PyObject* PyActMember_Get (PyObject* self, PyActMemberDef* actmem,
struct memberlist* members, char* name);
int PyActMember_Set (PyObject* self, PyActMemberDef* actmem,
struct memberlist* members, char* name, PyObject* val);

#endif /* INTRpytools_H */
----tools.c------------------>8-----SNIP!----->8-----------------------------
#include "tools.h"

PyObject*
PyObject_CallMethodA(ob, name, args)
PyObject* ob;
char* name;
PyObject* args;
{
PyObject* method;
PyObject* ret;

ret = NULL;
if(!ob) return NULL;
method = PyObject_GetAttrString(ob, name);
if(method) {
#if 0
D(pybug("Call method",method));
D(pybug("Parameters",args));
#endif
ret = PyEval_CallObject(method, args);
Py_DECREF(method);
}
return ret;
}

PyObject*
#ifdef HAVE_STDARG_PROTOTYPES
PyObject_CallMethod(PyObject* ob, char* name, char* fmt, ...)
#else
PyObject_CallMethod(va_alist) va_dcl
#endif
{
va_list va;
PyObject* ret;
PyObject* args;
#ifdef HAVE_STDARG_PROTOTYPES
va_start(va,fmt);
#else
PyObject* ob;
char* name;
char* fmt;
va_start(va);
ob = va_arg(va, PyObject*);
name = va_arg(va, char*);
fmt = va_arg(va, char*);
#endif

args = Py_VaBuildValue(fmt, va);
va_end(va);

if(!args) return NULL;

return PyObject_CallMethodA(ob,name,args);
}

static PyObject* listmembers(v, mlist)
PyObject* v;
PyActMemberDef* mlist;
{
int i, n;

for (n = 0; mlist[n].name != NULL; n++)
;
if( !v ) {
v = PyList_New(n);
}
if (v != NULL) {
for (i = 0; i < n; i++)
PyList_Append(v, PyString_FromString(mlist[i].name));
if (PyErr_Occurred()) {
Py_DECREF(v);
v = NULL;
} else {
PyList_Sort(v);
}
}
return v;
}

PyObject* PyActMember_Get(obj, actmem, members, name)
PyObject* obj;
PyActMemberDef* actmem;
struct memberlist* members;
char* name;
{
if (strcmp(name, "__members__") == 0) {
PyObject* memlist;
memlist = PyMember_Get((char*) obj, members, name);
if(memlist) return listmembers(memlist,actmem);
return NULL;
}
while( actmem && actmem->name != NULL ) {
if( strcmp(actmem->name, name) == 0 ) {
return actmem->getfunc(obj, name, actmem->extdata);
}
actmem++;
}

if( members ) {
return PyMember_Get((char *)obj, members, name);
}

PyErr_SetString(PyExc_AttributeError, name);
return NULL;
}

int PyActMember_Set(obj, actmem, members, name, val)
PyObject* obj;
PyActMemberDef* actmem;
struct memberlist* members;
char* name;
PyObject* val;
{
MARK;
while( actmem && actmem->name != NULL ) {
if (strcmp(actmem->name, name) == 0) {
if (actmem->readonly) {
PyErr_SetString(PyExc_TypeError, "readonly attribute");
return -1;
}
return actmem->setfunc(obj, name, val, actmem->extdata);
}
actmem++;
}

if( members ) {
MARK;
return PyMember_Set((char *)obj, members, name, val);
MARK;
}

PyErr_SetString(PyExc_AttributeError, name);
return -1;
}
----------------------------->8-----SNIP!----->8-----------------------------

[Actually, the call member functions are embarrassingly short, aren't
they...? :p]

-----------------------------------------------------------------------------
Dianne Kyra Hackborn "I believe that people have the right to decide
hackbod@mail.cs.orst.edu their own destinies; =people own themselves=."
Oregon State University -- Frank Zappa
//www.cs.orst.edu/~hackbod/