Subclassing ExtensionClass in C (this time with a solution)
Last week I asked a question about subclassing ExtensionClasses in C, but no one seemed to know how to do it. I then tried asking Jim Fulton about it, but he hasn't responded yet. So I decided to sit down and try to add the functionality myself. I am posting it here as some people on the list expressed an interest. I have tried to keep to the style of the current code, so those who have used ExtensionClass should be able to do Extension Subclasses in C without too much problem. Also, the modification is backward compatible (both source and binary), so it shouldn't break any current code. The one place where the programmer has to be careful is in making the size and layout of the instance structure of the new class is compatible with its base classes. I didn't include the checks done when subclassing from python, as you may want to have more than one non-pure mixin base class, or to extend the size of the instance structure in the subclass. To define a new Extension Subclass, follow the same steps as for a normal ExtensionClass, but instead of using PyExtensionClass_Export, use PyExtensionClass_ExportSubclass. This new function is similar to PyExtensionClass_Export, except that it takes an extra argument -- a tuple of base classes. The function takes ownership of the tuple. In cases where you only want to do single inheritance, there is a convenience routine called PyExtensionClass_ExportSubclassSingle. Its last argument is the PyExtensionClass structure for the base class. I have also attached a test module called TESC that tests out subclassing in C. Running it gives output something like:
import TESC a = TESC.Cls() b = TESC.Subcls() a.m1(), a.m2() ('m1 of Cls called', 'm2 of Cls called') b.m1(), b.m2(), b.m3() ('m1 of Subcls called', 'm2 of Cls called', 'm3 of Subcls called')
I have tested this module a fair amount, and it doesn't seem to introduce any problems with existing code, and is a fairly clean modification (I could have made the change a little cleaner, but that would have broken binary compatibility). So I was wondering if my patch could be integrated into the official ExtensionClass distribution. I am sure it is of use to more people than just me. James. -- Email: james@daa.com.au WWW: http://www.daa.com.au/~james/ --- ExtensionClass.c.orig Fri Mar 3 09:17:35 2000 +++ ExtensionClass.c Fri Mar 3 12:20:39 2000 @@ -1571,7 +1571,8 @@ fprintf(stderr,"Deallocating %s\n", self->tp_name); #endif Py_XDECREF(self->class_dictionary); - if (self->bases) + /* if this ExtensionClass was subclassed in python, free this info */ + if ((self->class_flags & EXTENSIONCLASS_PYSUBCLASS_FLAG) != 0) { /* If we are a subclass, then we strduped our name */ free(self->tp_name); @@ -1580,9 +1581,8 @@ if (self->tp_as_number) free(self->tp_as_number); if (self->tp_as_sequence) free(self->tp_as_sequence); if (self->tp_as_mapping) free(self->tp_as_mapping); - - Py_DECREF(self->bases); } + Py_XDECREF(self->bases); if (((PyExtensionClass*)self->ob_type) != self) Py_XDECREF(self->ob_type); PyMem_DEL(self); } @@ -3241,6 +3241,9 @@ #define copy_member(M) self->M=type->M copy_member(ob_size); copy_member(class_flags); + /* mark subclass as a python one, so subclass structures are freed on + * deallocation */ + self->class_flags |= EXTENSIONCLASS_PYSUBCLASS_FLAG; copy_member(tp_itemsize); copy_member(tp_print); @@ -3399,6 +3402,36 @@ return PyMapping_SetItemString(dict,name,(PyObject*)typ); } +/* bases is a tuple of base classes. This function absorbs the reference */ +static int +export_subclassed_type(PyObject *dict, char *name, PyExtensionClass *typ, + PyObject *bases) +{ + initializeBaseExtensionClass(typ); + + if (PyErr_Occurred()) return -1; + + /* set this up as a subclassed ExtensionClass */ + UNLESS (bases && PyTuple_Check(bases) && PyTuple_Size(bases)) { + PyErr_SetString(PyExc_TypeError, + "second argument must be a tuple of 1 or more base classes"); + return -1; + } + typ->bases = bases; + + if (PyDict_GetItem(typ->class_dictionary, py__module__) == NULL) + { + PyObject *modname = PyDict_GetItem(dict, py__name__); + if (modname != NULL) { + if (PyDict_SetItem(typ->class_dictionary, py__module__, modname) < 0) + return -1; + } + } + PyErr_Clear(); + + return PyMapping_SetItemString(dict,name,(PyObject*)typ); +} + static struct ExtensionClassCAPIstruct TrueExtensionClassCAPI = { export_type, @@ -3410,6 +3443,7 @@ (PyObject*)&PMethodType, PMethod_New, CMethod_issubclass, + export_subclassed_type, }; void --- ExtensionClass.h.orig Fri Mar 3 09:17:41 2000 +++ ExtensionClass.h Fri Mar 3 12:13:24 2000 @@ -174,6 +174,7 @@ #define EXTENSIONCLASS_INSTDICT_FLAG 1 << 4 #define EXTENSIONCLASS_NOINSTDICT_FLAG 1 << 5 #define EXTENSIONCLASS_BASICNEW_FLAG 1 << 6 +#define EXTENSIONCLASS_PYSUBCLASS_FLAG 1 << 7 /* The following flags are for use by extension class developers. */ #define EXTENSIONCLASS_USER_FLAG1 1 << 16 @@ -297,6 +298,25 @@ PyCObject_Import("ExtensionClass","CAPI"))) \ { PyExtensionClassCAPI->Export(D,N,&T); } +/* Export an Extension Subclass class in a given module dictionary with a + given name, ExtensionClass structure and set of base classes. It is + up to the programmer to make sure that the instance structures of the + base classes are compatible with that of the new class. + */ +#define PyExtensionClass_ExportSubclass(D,N,T,B) \ + if(PyExtensionClassCAPI || \ + (PyExtensionClassCAPI= (struct ExtensionClassCAPIstruct*) \ + PyCObject_Import("ExtensionClass","CAPI"))) \ + { PyExtensionClassCAPI->ExportSubclass(D,N,&T,B); } + +/* Export an Extension Subclass class in a given module dictionary with a + given name, ExtensionClass structure and single base class. It is up to + the programmer to make sure that the instance structures of the base + classes are compatible with that of the new class. + */ +#define PyExtensionClass_ExportSubclassSingle(D,N,T,B) \ + PyExtensionClass_ExportSubclass(D, N, T, Py_BuildValue("(O)",(PyObject*)&B)) + /* Convert a method list to a method chain. */ #define METHOD_CHAIN(DEF) { DEF, NULL } @@ -411,6 +431,8 @@ PyObject *MethodType; PyObject *(*Method_New)(PyObject *callable, PyObject *inst); int (*issubclass)(PyExtensionClass *sub, PyExtensionClass *type); + int (*ExportSubclass)(PyObject *dict, char *name, + PyExtensionClass *ob_type, PyObject *bases); } *PyExtensionClassCAPI = NULL; typedef struct { PyObject_HEAD } PyPureMixinObject; /* TESC -- Test ExtensionSubClass. I know it is not a great name :) */ #include "ExtensionClass.h" staticforward PyExtensionClass ClsType; staticforward PyExtensionClass SubclsType; static void dealloc(PyObject *self) { PyMem_DEL(self); } static PyObject * getattr(PyObject *self, char *attr) { return Py_FindMethod(cls_methods, self, attr); } static PyObject * cls_m1(PyObject *self, PyObject *args) { return PyString_FromString("m1 of Cls called"); } static PyObject * cls_m2(PyObject *self, PyObject *args) { return PyString_FromString("m2 of Cls called"); } static struct PyMethodDef cls_methods[] = { {"m1", (PyCFunction)cls_m1, 1, ""}, {"m2", (PyCFunction)cls_m2, 1, ""}, {NULL, NULL} }; static PyObject * sub_m1(PyObject *self, PyObject *args) { return PyString_FromString("m1 of Subcls called"); } static PyObject * sub_m3(PyObject *self, PyObject *args) { return PyString_FromString("m3 of Subcls called"); } static struct PyMethodDef sub_methods[] = { {"m1", (PyCFunction)sub_m1, 1, ""}, {"m3", (PyCFunction)sub_m3, 1, ""}, {NULL, NULL} }; static PyExtensionClass ClsType = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "Cls", /*tp_name*/ sizeof(PyExtensionClass), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)dealloc, /*tp_dealloc*/ (printfunc)0, /*tp_print*/ (getattrfunc)getattr, /*tp_getattr*/ (setattrfunc)0, /*tp_setattr*/ (cmpfunc)0, /*tp_compare*/ (reprfunc)0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ (hashfunc)0, /*tp_hash*/ (ternaryfunc)0, /*tp_call*/ (reprfunc)0, /*tp_str*/ (getattrofunc)0, /*tp_getattr with object key*/ (setattrofunc)0, /*tp_setattr with object key*/ /* Space for future expansion */ 0L,0L, NULL, /* Documentation string */ METHOD_CHAIN(cls_methods) }; static PyExtensionClass SubclsType = { PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ "Subcls", /*tp_name*/ sizeof(PyExtensionClass), /*tp_basicsize*/ 0, /*tp_itemsize*/ /* methods */ (destructor)dealloc, /*tp_dealloc*/ (printfunc)0, /*tp_print*/ (getattrfunc)getattr, /*tp_getattr*/ (setattrfunc)0, /*tp_setattr*/ (cmpfunc)0, /*tp_compare*/ (reprfunc)0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ (hashfunc)0, /*tp_hash*/ (ternaryfunc)0, /*tp_call*/ (reprfunc)0, /*tp_str*/ (getattrofunc)0, /*tp_getattr with object key*/ (setattrofunc)0, /*tp_setattr with object key*/ /* Space for future expansion */ 0L,0L, NULL, /* Documentation string */ METHOD_CHAIN(sub_methods) }; static struct PyMethodDef methods[] = {{NULL, NULL}}; void initTESC() { PyObject *m, *d; m = Py_InitModule("TESC", methods); d = PyModule_GetDict(m); PyExtensionClass_Export(d, "Cls", ClsType); PyExtensionClass_ExportSubclassSingle(d, "Subcls", SubclsType, ClsType); }
James Henstridge wrote:
Last week I asked a question about subclassing ExtensionClasses in C, but no one seemed to know how to do it. I then tried asking Jim Fulton about it, but he hasn't responded yet.
He's on vacation. Get in line. ;)
So I decided to sit down and try to add the functionality myself. I am posting it here as some people on the list expressed an interest. I have tried to keep to the style of the current code, so those who have used ExtensionClass should be able to do Extension Subclasses in C without too much problem. Also, the modification is backward compatible (both source and binary), so it shouldn't break any current code.
Ok, good.
The one place where the programmer has to be careful is in making the size and layout of the instance structure of the new class is compatible with its base classes. I didn't include the checks done when subclassing from python, as you may want to have more than one non-pure mixin base class, or to extend the size of the instance structure in the subclass.
To define a new Extension Subclass, follow the same steps as for a normal ExtensionClass, but instead of using PyExtensionClass_Export, use PyExtensionClass_ExportSubclass. This new function is similar to PyExtensionClass_Export, except that it takes an extra argument -- a tuple of base classes. The function takes ownership of the tuple.
In cases where you only want to do single inheritance, there is a convenience routine called PyExtensionClass_ExportSubclassSingle. Its last argument is the PyExtensionClass structure for the base class.
I have also attached a test module called TESC that tests out subclassing in C. Running it gives output something like:
import TESC a = TESC.Cls() b = TESC.Subcls() a.m1(), a.m2() ('m1 of Cls called', 'm2 of Cls called') b.m1(), b.m2(), b.m3() ('m1 of Subcls called', 'm2 of Cls called', 'm3 of Subcls called')
I have tested this module a fair amount, and it doesn't seem to introduce any problems with existing code, and is a fairly clean modification (I could have made the change a little cleaner, but that would have broken binary compatibility). So I was wondering if my patch could be integrated into the official ExtensionClass distribution. I am sure it is of use to more people than just me.
This will have to go by Jim, of course. So far it sounds good to me, but what do I know (hint, not much). -Michel
participants (2)
-
James Henstridge -
Michel Pelletier