Commits

wlav committed a2cbc52

allow customization of __init__ and __del__ in a base class

  • Participants
  • Parent commits 646b4e8
  • Branches reflex-support

Comments (0)

Files changed (5)

File pypy/module/cppyy/interp_cppyy.py

             dname = capi.c_datamember_name(self.space, self, i)
             if dname: alldir.append(self.space.wrap(dname))
         return self.space.newlist(alldir)
-        
 
 W_CPPNamespace.typedef = TypeDef(
     'CPPNamespace',
     __cmp__ = interp2app(W_CPPInstance.instance__cmp__),
     __repr__ = interp2app(W_CPPInstance.instance__repr__),
     __destruct__ = interp2app(W_CPPInstance.destruct),
+    __del__ = interp2app(W_CPPInstance.__del__),
 )
 W_CPPInstance.typedef.acceptable_as_base_class = True
 
     state = space.fromcache(State)
     return space.call_function(state.w_fngen_callback, w_callable, space.wrap(npar))
 
-def wrap_cppobject(space, rawobject, cppclass,
+def wrap_cppobject(space, rawobject, cppclass, w_pycppclass=None,
                    do_cast=True, python_owns=False, is_ref=False, fresh=False):
-    rawobject = rffi.cast(capi.C_OBJECT, rawobject)
+    # Wrap a C++ object for use on the python side:
+    #     rawobject    : address pointing to the C++ object
+    #     cppclass     : rpython-side C++ class proxy
+    #     w_pycppclass : wrapped python class (may be derived, by a developer)
+    #     do_cast      : calculate offset between given type and run-time type
+    #     python_owns  : sets _python_owns flag (True: will delete C++ on __del__)
+    #     is_ref       : True of rawobject is a pointer to the C++ object (i.e. &this)
+    #     fresh        : True if newly created object (e.g. from constructor)
 
     # cast to actual if requested and possible
-    w_pycppclass = None
+    rawobject = rffi.cast(capi.C_OBJECT, rawobject)
     if do_cast and rawobject:
         actual = capi.c_actual_class(space, cppclass, rawobject)
         if actual != cppclass.handle:
                 # the variables are re-assigned yet)
                 pass
 
-    if w_pycppclass is None:
-        w_pycppclass = get_pythonized_cppclass(space, cppclass.handle)
-
     # try to recycle existing object if this one is not newly created
     if not fresh and rawobject:
         obj = memory_regulator.retrieve(rawobject)
         if obj is not None and obj.cppclass is cppclass:
             return obj
 
+    # this is slow, so only call if really necessary (it may have been provided or set
+    # when calculating the casting offset above)
+    if w_pycppclass is None:
+        w_pycppclass = get_pythonized_cppclass(space, cppclass.handle)
+
     # fresh creation
     w_cppinstance = space.allocate_instance(W_CPPInstance, w_pycppclass)
     cppinstance = space.interp_w(W_CPPInstance, w_cppinstance, can_be_None=False)
     except Exception:
         # accept integer value as address
         rawobject = rffi.cast(capi.C_OBJECT, space.uint_w(w_obj))
+
     w_cppclass = space.findattr(w_pycppclass, space.wrap("_cpp_proxy"))
     if not w_cppclass:
         w_cppclass = scope_byname(space, space.str_w(w_pycppclass))
         if not w_cppclass:
             raise OperationError(space.w_TypeError,
                 space.wrap("no such class: %s" % space.str_w(w_pycppclass)))
+        w_pycppclass = None
     cppclass = space.interp_w(W_CPPClass, w_cppclass, can_be_None=False)
-    return wrap_cppobject(space, rawobject, cppclass, do_cast=cast, python_owns=owns)
+    return wrap_cppobject(space, rawobject, cppclass, w_pycppclass, do_cast=cast, python_owns=owns)

File pypy/module/cppyy/pythonify.py

 
 def make_new(class_name):
     def __new__(cls, *args):
-        # create a place-holder only as there may be a derived class defined
-        import cppyy
-        instance = cppyy.bind_object(0, class_name, True)
-        if not instance.__class__ is cls:
-            instance.__class__ = cls     # happens for derived class
-        return instance
+        # create a place-holder (python instance + nullptr) only as there may
+        # be a derived class defined; __init__ allocates and fills the ptr
+        import cppyy     # lazy
+        return cppyy.bind_object(0, cls, True)
     return __new__
 
 def make_pycppclass(scope, class_name, final_class_name, cppclass):
          "__new__"      : make_new(class_name),
          }
     pycppclass = metacpp(class_name, _drop_cycles(bases), d)
- 
+
     # cache result early so that the class methods can find the class itself
     setattr(scope, final_class_name, pycppclass)
 
     # needs to run first, so that the generic pythonizations can use them
     import cppyy
     cppyy._register_class(pycppclass)
-    _pythonize(pycppclass)
-    return pycppclass
+    return _pythonize(pycppclass)
 
 def make_cpptemplatetype(scope, template_name):
     return CPPTemplate(template_name, scope)
 
     pycppitem = None
 
-    # classes
+    # scopes
     cppitem = cppyy._scope_byname(true_name)
     if cppitem:
         if cppitem.is_namespace():
             pycppitem = make_cppnamespace(scope, true_name, cppitem)
-            setattr(scope, name, pycppitem)
         else:
             pycppitem = make_pycppclass(scope, true_name, name, cppitem)
+        setattr(scope, name, pycppitem)
 
     # enums (special case)
     if not cppitem:
     else:
         return python_style_getitem(self, slice_or_idx)
 
-_pythonizations = {}
+_specific_pythonizations = {}
+_global_pythonizations = []
 def _pythonize(pyclass):
 
     try:
-        _pythonizations[pyclass.__name__](pyclass)
+        cbs = _specific_pythonizations[pyclass.__name__]
+        for cb in cbs:
+            res = cb(pyclass)
+            if res: pyclass = res
     except KeyError:
         pass
 
+    for cb in _global_pythonizations:
+        res = cb(pyclass)
+        if res: pyclass = res
+
     # general note: use 'in pyclass.__dict__' rather than 'hasattr' to prevent
     # adding pythonizations multiple times in derived classes
 
                 while i != self.end():
                     yield i.__deref__()
                     i.__preinc__()
-                i.destruct()
+                i.__destruct__()
                 raise StopIteration
             pyclass.__iter__ = __iter__
         # else: rely on numbered iteration
         pyclass.__getitem__ = getitem
         pyclass.__len__     = return2
 
+    return pyclass
+
 _loaded_dictionaries = {}
 def load_reflection_info(name):
     """Takes the name of a library containing reflection info, returns a handle
         lib = cppyy._load_dictionary(name)
         _loaded_dictionaries[name] = lib
         return lib
-    
+
 def _init_pythonify():
     # cppyy should not be loaded at the module level, as that will trigger a
     # call to space.getbuiltinmodule(), which will cause cppyy to be loaded
     sys.modules['cppyy.gbl.std'] = gbl.std
 
 # user-defined pythonizations interface
-_pythonizations = {}
 def add_pythonization(class_name, callback):
     """Takes a class name and a callback. The callback should take a single
     argument, the class proxy, and is called the first time the named class
     is bound."""
     if not callable(callback):
         raise TypeError("given '%s' object is not callable" % str(callback))
-    _pythonizations[class_name] = callback
+    if class_name == '*':
+        _global_pythonizations.append(callback)
+    else:
+        try:
+            _specific_pythonizations[class_name].append(callback)
+        except KeyError:
+            _specific_pythonizations[class_name] = [callback]

File pypy/module/cppyy/test/example01.h

 public:
    example01a(int a) : example01(a) {}
 };
+
+class example01b : public example01 {
+public:
+   example01b(int a) : example01(a) {}
+};
+
+class example01c : public example01 {
+public:
+   example01c(int a) : example01(a) {}
+};
+
+class example01d : public example01 {
+public:
+   example01d(int a) : example01(a) {}
+};

File pypy/module/cppyy/test/example01.xml

   <class name="payload" />
   <class name="example01" />
   <class name="example01_t" />
-  <class name="example01a" />
+  <class pattern="example01?" />
   <class name="std::string" />
   <class name="z_" />
 

File pypy/module/cppyy/test/test_pythonify.py

 
         assert example01.getCount() == 0
 
+        class MyClass3(example01):
+            def __init__(self, *args):
+                example01.__init__(self, *args)
+
+        raises(TypeError,  MyClass3, 'hi')
+        o = MyClass3(312)
+        assert type(o) == MyClass3
+        assert example01.getCount() == 1
+        assert o.m_somedata == 312
+        o.__destruct__()
+
+        assert example01.getCount() == 0
+
+        class MyClass4(example01):
+            pycount = 0
+            def __init__(self, *args):
+                example01.__init__(self, *args)
+                MyClass4.pycount += 1
+            def __del__(self):
+                example01.__del__(self)
+                MyClass4.pycount -= 1
+
+        o = MyClass4()
+        assert type(o) == MyClass4
+        assert example01.getCount() == 1
+        assert MyClass4.pycount == 1
+        del o
+
+        import gc
+        gc.collect()
+
+        assert MyClass4.pycount == 0
+        assert example01.getCount() == 0
+
 
 class AppTestPYTHONIFY_UI:
     spaceconfig = dict(usemodules=['cppyy', '_rawffi', 'itertools'])
 
         import cppyy
 
+        # simple pythonization
         def example01a_pythonize(pyclass):
-            assert pyclass.__name__ == 'example01a'
+            import cppyy
+            assert issubclass(pyclass, cppyy.gbl.example01)
             def getitem(self, idx):
                 return self.addDataToInt(idx)
             pyclass.__getitem__ = getitem
-
         cppyy.add_pythonization('example01a', example01a_pythonize)
 
         e = cppyy.gbl.example01a(1)
         assert e[1] == 2
         assert e[5] == 6
 
+        # stacked pythonization
+        cppyy.add_pythonization('example01b', example01a_pythonize)
+        def example01b_pythonize(pyclass):
+            assert pyclass.__name__ == 'example01b'
+            def _len(self):
+                return self.m_somedata
+            pyclass.__len__ = _len
+        cppyy.add_pythonization('example01b', example01a_pythonize)
+        cppyy.add_pythonization('example01b', example01b_pythonize)
+
+        e = cppyy.gbl.example01b(42)
+
+        assert e[0] == 42
+        assert e[1] == 43
+        assert len(e) == 42
+
+        # class replacement
+        def example01c_pythonize(pyclass):
+            if pyclass.__name__ == 'example01c':
+                class custom(pyclass):
+                    pycount = 0
+                    def __init__(self, *args):
+                        custom.pycount += 1
+                        pyclass.__init__(self, *args)
+                    def __del__(self):
+                        pyclass.__del__(self)
+                        custom.pycount -= 1
+                return custom
+        cppyy.add_pythonization('*', example01c_pythonize)
+
+        e = cppyy.gbl.example01c(88)
+        assert type(e) == cppyy.gbl.example01c
+        assert cppyy.gbl.example01c.getCount() == 1
+        assert cppyy.gbl.example01c.pycount == 1
+        assert e.m_somedata == 88
+        del e
+
+        import gc
+        gc.collect()
+
+        assert cppyy.gbl.example01c.pycount == 0
+        assert cppyy.gbl.example01c.getCount() == 0
+
+        # alt class replacement
+        def example01d_pythonize(pyclass):
+            if pyclass.__name__ == 'example01d':
+                d = {}
+                d['pycount'] = 0
+                def __init__(self, *args):
+                    self.__class__.pycount += 1
+                    pyclass.__init__(self, *args)
+                d['__init__'] = __init__
+                def __del__(self, *args):
+                    self.__class__.pycount -= 1
+                    pyclass.__del__(self)
+                d['__del__'] = __del__
+                return pyclass.__class__('_'+pyclass.__name__, (pyclass,), d)
+        cppyy.add_pythonization('*', example01d_pythonize)
+
+        e = cppyy.gbl.example01d(101)
+        assert type(e) == cppyy.gbl.example01d
+        assert cppyy.gbl.example01d.getCount() == 1
+        assert cppyy.gbl.example01d.pycount == 1
+        assert e.m_somedata == 101
+        del e
+
+        import gc
+        gc.collect()
+
+        assert cppyy.gbl.example01d.pycount == 0
+        assert cppyy.gbl.example01d.getCount() == 0
+
+
     def test02_fragile_pythonizations(self):
         """Test pythonizations error reporting"""