diff --git a/examples/footest.py b/examples/footest.py index fa837f3..9fb9521 100644 --- a/examples/footest.py +++ b/examples/footest.py @@ -88,7 +88,6 @@ class TestFoo(unittest.TestCase): def test_static_get_attribute(self): self.assertEqual(foo.SomeObject.staticData, "Hello Static World!") - # Not working :-( #def test_static_set_attribute(self): # foo.SomeObject.staticData = "Foo Bar Zbr" # self.assertEqual(foo.SomeObject.staticData, "Foo Bar Zbr") diff --git a/pybindgen/cppattribute.py b/pybindgen/cppattribute.py index eccc2be..ff70929 100644 --- a/pybindgen/cppattribute.py +++ b/pybindgen/cppattribute.py @@ -7,8 +7,26 @@ from typehandlers import codesink import settings +class PyGetter(ForwardWrapperBase): + """generates a getter, for use in a PyGetSetDef table""" + def generate(self, code_sink): + """Generate the code of the getter to the given code sink""" + raise NotImplementedError + def generate_call(self): + """(not actually called)""" + raise AssertionError -class CppInstanceAttributeGetter(ForwardWrapperBase): +class PySetter(ReverseWrapperBase): + """generates a setter, for use in a PyGetSetDef table""" + def generate(self, code_sink): + """Generate the code of the setter to the given code sink""" + raise NotImplementedError + def generate_python_call(self): + """(not actually called)""" + raise AssertionError + + +class CppInstanceAttributeGetter(PyGetter): ''' A getter for a C++ instance attribute. ''' @@ -45,7 +63,7 @@ class CppInstanceAttributeGetter(ForwardWrapperBase): code_sink.writeln('}') -class CppStaticAttributeGetter(ForwardWrapperBase): +class CppStaticAttributeGetter(PyGetter): ''' A getter for a C++ class static attribute. ''' @@ -80,7 +98,9 @@ class CppStaticAttributeGetter(ForwardWrapperBase): code_sink.writeln('}') -class CppInstanceAttributeSetter(ReverseWrapperBase): + + +class CppInstanceAttributeSetter(PySetter): ''' A setter for a C++ instance attribute. ''' @@ -131,7 +151,7 @@ class CppInstanceAttributeSetter(ReverseWrapperBase): code_sink.writeln('}') -class CppStaticAttributeSetter(ReverseWrapperBase): +class CppStaticAttributeSetter(PySetter): ''' A setter for a C++ class static attribute. ''' @@ -183,18 +203,33 @@ class CppStaticAttributeSetter(ReverseWrapperBase): code_sink.writeln('}') -class PyDescrGenerator(object): +class PyMetaclass(object): """ - Class that generates a Python descriptor type. + Class that generates a Python metaclass """ - def __init__(self, name, descr_get, descr_set): + def __init__(self, name, parent_metaclass_expr, getsets=None): + """ + name -- name of the metaclass (should normally end with Meta) + parent_metaclass_expr -- C expression that should give a + pointer to the parent metaclass + (should have a C type of + PyTypeObject*) + getsets -- name of a PyGetSetDef C array variable, or None + """ + assert getsets is None or isinstance(getsets, PyGetSetDef) + assert isinstance(name, str) + assert isinstance(parent_metaclass_expr, str) + self.name = name - self.descr_get = descr_get - self.descr_set = descr_set prefix = settings.name_prefix.capitalize() self.pytypestruct = "Py%s%s_Type" % (prefix, self.name) + self.parent_metaclass_expr = parent_metaclass_expr + self.getsets = getsets - def generate(self, code_sink): + def generate(self, code_sink, module): + """ + Generate the metaclass to code_sink and register it in the module. + """ code_sink.writeln(''' PyTypeObject %(pytypestruct)s = { PyObject_HEAD_INIT(NULL) @@ -217,7 +252,7 @@ PyTypeObject %(pytypestruct)s = { 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT, /* tp_flags */ + Py_TPFLAGS_DEFAULT|Py_TPFLAGS_HAVE_GC|Py_TPFLAGS_BASETYPE, /* tp_flags */ 0, /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ @@ -227,11 +262,11 @@ PyTypeObject %(pytypestruct)s = { 0, /* tp_iternext */ 0, /* tp_methods */ 0, /* tp_members */ - 0, /* tp_getset */ + %(getset)s, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ - (descrgetfunc) %(descr_get)s, /* tp_descr_get */ - (descrsetfunc) %(descr_set)s, /* tp_descr_set */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ 0, /* tp_dictoffset */ 0, /* tp_init */ 0, /* tp_alloc */ @@ -246,6 +281,82 @@ PyTypeObject %(pytypestruct)s = { 0 /* tp_del */ }; ''' % dict(pytypestruct=self.pytypestruct, name=self.name, - descr_get=(self.descr_get or '0'), - descr_set=(self.descr_set or '0'))) + getset=(self.getsets and self.getsets.cname or '0'))) + module.after_init.write_code(""" +%(pytypestruct)s.tp_base = %(parent_metaclass)s; +/* Some fields need to be manually inheritted from the parent metaclass */ +%(pytypestruct)s.tp_traverse = %(parent_metaclass)s->tp_traverse; +%(pytypestruct)s.tp_clear = %(parent_metaclass)s->tp_clear; +%(pytypestruct)s.tp_is_gc = %(parent_metaclass)s->tp_is_gc; +PyType_Ready(&%(pytypestruct)s); +""" % dict(pytypestruct=self.pytypestruct, parent_metaclass=self.parent_metaclass_expr)) + + + +class PyGetSetDef(object): + """ + Class that generates a PyGetSet table + """ + def __init__(self, cname): + """ + cname -- C name of the getset table + """ + self.cname = cname + self.attributes = [] # (name, getter, setter) + + def add_attribute(self, name, getter, setter): + """ + Add a new attribute + name -- attribute name + getter -- a PyGetter object, or None + setter -- a PySetter object, or None + """ + assert getter is None or isinstance(getter, PyGetter) + assert setter is None or isinstance(setter, PySetter) + self.attributes.append((name, getter, setter)) + + def generate(self, code_sink): + """ + Generate the getset table, return the table C name or '0' if + the table is empty + """ + if not self.attributes: + return '0' + + for name, getter, setter in self.attributes: + if getter is not None: + getter.generate(code_sink) + if setter is not None: + setter.generate(code_sink) + + code_sink.writeln("static PyGetSetDef %s[] = {" % self.cname) + code_sink.indent() + for name, getter, setter in self.attributes: + code_sink.writeln('{') + code_sink.indent() + code_sink.writeln('"%s", /* attribute name */' % name) + + ## getter + getter_c_name = (getter and getter.c_function_name or "NULL") + code_sink.writeln( + '(getter) %s, /* C function to get the attribute */' + % getter_c_name) + + ## setter + setter_c_name = setter and setter.c_function_name or "NULL" + code_sink.writeln( + '(setter) %s, /* C function to set the attribute */' + % setter_c_name) + + code_sink.writeln('NULL, /* optional doc string */') + code_sink.writeln('NULL /* optional additional data ' + 'for getter and setter */') + code_sink.unindent() + code_sink.writeln('},') + code_sink.writeln('{ NULL, NULL, NULL, NULL, NULL }') + code_sink.unindent() + code_sink.writeln('};') + + return self.cname + diff --git a/pybindgen/cppclass.py b/pybindgen/cppclass.py index eb394e8..425cd49 100644 --- a/pybindgen/cppclass.py +++ b/pybindgen/cppclass.py @@ -7,7 +7,7 @@ from typehandlers.base import ForwardWrapperBase, Parameter, ReturnValue from cppmethod import CppMethod, CppConstructor, CppNoConstructor from cppattribute import (CppInstanceAttributeGetter, CppInstanceAttributeSetter, CppStaticAttributeGetter, CppStaticAttributeSetter, - PyDescrGenerator) + PyGetSetDef, PyMetaclass) import settings @@ -52,7 +52,7 @@ class CppClass(object): ' (iternextfunc)%(tp_iternext)s, /* tp_iternext */\n' ' (struct PyMethodDef*)%(tp_methods)s, /* tp_methods */\n' ' (struct PyMemberDef*)0, /* tp_members */\n' - ' (struct PyGetSetDef*)%(tp_getset)s, /* tp_getset */\n' + ' %(tp_getset)s, /* tp_getset */\n' ' NULL, /* tp_base */\n' ' NULL, /* tp_dict */\n' ' (descrgetfunc)%(tp_descr_get)s, /* tp_descr_get */\n' @@ -88,11 +88,16 @@ class CppClass(object): self.name = name self.methods = [] # (name, wrapper) pairs self.constructors = [] # (name, wrapper) pairs - self.attributes = [] # (name, getter, setter) pairs self.slots = dict() + prefix = settings.name_prefix.capitalize() self.pystruct = "Py%s%s" % (prefix, self.name) + self.metaclass_name = "%sMeta" % self.pystruct self.pytypestruct = "Py%s%s_Type" % (prefix, self.name) + + self.instance_attributes = PyGetSetDef("%s__getsets" % self.pystruct) + self.static_attributes = PyGetSetDef("%s__getsets" % self.metaclass_name) + self.parent = parent assert parent is None or isinstance(parent, CppClass) assert (incref_method is None and decref_method is None) \ @@ -160,11 +165,8 @@ class CppClass(object): """ assert isinstance(value_type, ReturnValue) getter = CppStaticAttributeGetter(value_type, self, name) - ## it is hopeless, descriptor setters are not even invoked - ## when accessed through a class, only from instances. :( - #setter = CppStaticAttributeSetter(value_type, self, name) - setter = None - self.attributes.append((name, getter, setter)) + setter = CppStaticAttributeSetter(value_type, self, name) + self.static_attributes.add_attribute(name, getter, setter) def add_instance_attribute(self, value_type, name): """ @@ -174,7 +176,7 @@ class CppClass(object): assert isinstance(value_type, ReturnValue) getter = CppInstanceAttributeGetter(value_type, self, name) setter = CppInstanceAttributeSetter(value_type, self, name) - self.attributes.append((name, getter, setter)) + self.instance_attributes.add_attribute(name, getter, setter) def generate_forward_declarations(self, code_sink): """Generates forward declarations for the instance and type @@ -195,12 +197,36 @@ typedef struct { def generate(self, code_sink, module, docstring=None): """Generates the class to a code sink""" + ## generate getsets + instance_getsets = self.instance_attributes.generate(code_sink) + self.slots.setdefault("tp_getset", instance_getsets) + static_getsets = self.static_attributes.generate(code_sink) + ## --- register the class type in the module --- module.after_init.write_code("/* Register the '%s' class */" % self.name) + + ## generate a metaclass if needed + if static_getsets == '0': + metaclass = None + else: + if self.parent is None: + parent_typestruct = 'PyBaseObject_Type' + else: + parent_typestruct = self.parent.pytypestruct + metaclass = PyMetaclass(self.metaclass_name, + "%s.ob_type" % parent_typestruct, + self.static_attributes) + metaclass.generate(code_sink, module) + if self.parent is not None: assert isinstance(self.parent, CppClass) module.after_init.write_code('%s.tp_base = &%s;' % (self.pytypestruct, self.parent.pytypestruct)) + + if metaclass is not None: + module.after_init.write_code('%s.ob_type = &%s;' % + (self.pytypestruct, metaclass.pytypestruct)) + module.after_init.write_error_check('PyType_Ready(&%s)' % (self.pytypestruct,)) module.after_init.write_code( @@ -235,7 +261,6 @@ typedef struct { have_constructor = False code_sink.writeln() - ## generate the method wrappers method_defs = [] for meth_name, meth_wrapper in self.methods: @@ -252,98 +277,6 @@ typedef struct { code_sink.unindent() code_sink.writeln("};") - - ## generate descriptor table for instance attributes - if self.attributes: - have_instance_attributes = False - for name, getter, setter in self.attributes: - - if isinstance(getter, CppInstanceAttributeGetter): - have_instance_attributes = True - getter.generate(code_sink) - ## getter.c_function_name is then added to the - ## tp_getset table, further below. - - elif (isinstance(getter, CppStaticAttributeGetter) - or isinstance(setter, CppStaticAttributeSetter)): - ## Static attributes are a bit more tricky.. :-/ - ## mainly because PyDecr_NewGetSet generates a - ## descriptor that disables itself when called - ## from a class; it only handles instances. - ## Therefore, we need to create our own descriptor - ## python type... - if getter is None: - getter_c_name = None - else: - getter_c_name = getter.c_function_name - getter.generate(code_sink) - - if setter is None: - setter_c_name = None - else: - setter_c_name = setter.c_function_name - setter.generate(code_sink) - - descriptor = PyDescrGenerator( - '%s_%sDescr' % (self.pystruct, name), - getter_c_name, setter_c_name) - descriptor.generate(code_sink) - module.after_init.write_error_check( - 'PyType_Ready(&%s)' % (descriptor.pytypestruct)) - module.after_init.write_code( - 'PyDict_SetItemString(%s.tp_dict, "%s", PyObject_New(PyObject, &%s));' - % (self.pytypestruct, name, descriptor.pytypestruct)) - - ## generate setter wrapper function body - if isinstance(setter, CppInstanceAttributeSetter): - have_instance_attributes = True - setter.generate(code_sink) - - if have_instance_attributes: - getset_table_name = "_%s__getsets" % self.pystruct - self.slots.setdefault("tp_getset", getset_table_name) - code_sink.writeln("static PyGetSetDef %s[] = {" % getset_table_name) - code_sink.indent() - for name, getter, setter in self.attributes: - if isinstance(getter, CppStaticAttributeGetter) \ - or isinstance(setter, CppStaticAttributeGetter): - continue # static attributes do not use tp_getset - code_sink.writeln('{') - code_sink.indent() - code_sink.writeln('"%s", /* attribute name */' % name) - - ## getter - if isinstance(getter, CppInstanceAttributeGetter): - getter_c_name = getter.c_function_name - else: - getter_c_name = "NULL" - code_sink.writeln( - '(getter) %s, /* C function to get the attribute */' - % getter_c_name) - - ## setter - if isinstance(setter, CppInstanceAttributeSetter): - setter_c_name = setter.c_function_name - else: - setter_c_name = "NULL" - code_sink.writeln( - '(setter) %s, /* C function to set the attribute */' - % setter_c_name) - - code_sink.writeln('NULL, /* optional doc string */') - code_sink.writeln('NULL /* optional additional data ' - 'for getter and setter */') - code_sink.unindent() - code_sink.writeln('},') - code_sink.writeln('{ NULL, NULL, NULL, NULL, NULL }') - code_sink.unindent() - code_sink.writeln('};') - else: - self.slots.setdefault("tp_getset", 'NULL') - else: - self.slots.setdefault("tp_getset", 'NULL') - - ## generate the destructor tp_dealloc_function_name = "_wrap_%s__tp_dealloc" % (self.pystruct,) if have_constructor: