[pypy-commit] pypy reflex-support: allow customization of __init__ and __del__ in a base class
wlav
noreply at buildbot.pypy.org
Thu May 15 09:33:37 CEST 2014
Author: Wim Lavrijsen <WLavrijsen at lbl.gov>
Branch: reflex-support
Changeset: r71529:a2cbc5204fef
Date: 2014-05-15 00:32 -0700
http://bitbucket.org/pypy/pypy/changeset/a2cbc5204fef/
Log: allow customization of __init__ and __del__ in a base class
diff --git a/pypy/module/cppyy/interp_cppyy.py b/pypy/module/cppyy/interp_cppyy.py
--- a/pypy/module/cppyy/interp_cppyy.py
+++ b/pypy/module/cppyy/interp_cppyy.py
@@ -883,7 +883,6 @@
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',
@@ -1182,6 +1181,7 @@
__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
@@ -1230,12 +1230,19 @@
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:
@@ -1251,15 +1258,17 @@
# 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)
@@ -1291,11 +1300,13 @@
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)
diff --git a/pypy/module/cppyy/pythonify.py b/pypy/module/cppyy/pythonify.py
--- a/pypy/module/cppyy/pythonify.py
+++ b/pypy/module/cppyy/pythonify.py
@@ -140,12 +140,10 @@
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):
@@ -175,7 +173,7 @@
"__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)
@@ -204,8 +202,7 @@
# 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)
@@ -222,14 +219,14 @@
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:
@@ -316,14 +313,22 @@
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
@@ -373,7 +378,7 @@
while i != self.end():
yield i.__deref__()
i.__preinc__()
- i.destruct()
+ i.__destruct__()
raise StopIteration
pyclass.__iter__ = __iter__
# else: rely on numbered iteration
@@ -407,6 +412,8 @@
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
@@ -418,7 +425,7 @@
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
@@ -464,11 +471,16 @@
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]
diff --git a/pypy/module/cppyy/test/example01.h b/pypy/module/cppyy/test/example01.h
--- a/pypy/module/cppyy/test/example01.h
+++ b/pypy/module/cppyy/test/example01.h
@@ -113,3 +113,18 @@
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) {}
+};
diff --git a/pypy/module/cppyy/test/example01.xml b/pypy/module/cppyy/test/example01.xml
--- a/pypy/module/cppyy/test/example01.xml
+++ b/pypy/module/cppyy/test/example01.xml
@@ -3,7 +3,7 @@
<class name="payload" />
<class name="example01" />
<class name="example01_t" />
- <class name="example01a" />
+ <class pattern="example01?" />
<class name="std::string" />
<class name="z_" />
diff --git a/pypy/module/cppyy/test/test_pythonify.py b/pypy/module/cppyy/test/test_pythonify.py
--- a/pypy/module/cppyy/test/test_pythonify.py
+++ b/pypy/module/cppyy/test/test_pythonify.py
@@ -371,6 +371,40 @@
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'])
@@ -386,12 +420,13 @@
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)
@@ -400,6 +435,79 @@
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"""
More information about the pypy-commit
mailing list