Author: Wim Lavrijsen <wlavrij...@lbl.gov> Branch: cppyy-packaging Changeset: r94596:10c6393b2cd5 Date: 2018-05-15 20:59 -0700 http://bitbucket.org/pypy/pypy/changeset/10c6393b2cd5/
Log: pythonization improvements diff --git a/pypy/module/_cppyy/__init__.py b/pypy/module/_cppyy/__init__.py --- a/pypy/module/_cppyy/__init__.py +++ b/pypy/module/_cppyy/__init__.py @@ -19,12 +19,14 @@ '_bind_object' : 'interp_cppyy._bind_object', 'bind_object' : 'interp_cppyy.bind_object', 'move' : 'interp_cppyy.move', + '_pin_type' : 'interp_cppyy._pin_type', } appleveldefs = { '_post_import_startup' : 'pythonify._post_import_startup', + 'Template' : 'pythonify.CPPTemplate', 'add_pythonization' : 'pythonify.add_pythonization', - 'Template' : 'pythonify.CPPTemplate', + 'remove_pythonization' : 'pythonify.remove_pythonization', } def __init__(self, space, *args): diff --git a/pypy/module/_cppyy/capi/loadable_capi.py b/pypy/module/_cppyy/capi/loadable_capi.py --- a/pypy/module/_cppyy/capi/loadable_capi.py +++ b/pypy/module/_cppyy/capi/loadable_capi.py @@ -676,7 +676,7 @@ space.setattr(w_pycppclass, space.newtext(m1), space.getattr(w_pycppclass, space.newtext(m2))) -def pythonize(space, name, w_pycppclass): +def pythonize(space, w_pycppclass, name): if name == "string": space.setattr(w_pycppclass, space.newtext("c_str"), _pythonizations["stdstring_c_str"]) _method_alias(space, w_pycppclass, "_cppyy_as_builtin", "c_str") diff --git a/pypy/module/_cppyy/helper.py b/pypy/module/_cppyy/helper.py --- a/pypy/module/_cppyy/helper.py +++ b/pypy/module/_cppyy/helper.py @@ -59,26 +59,6 @@ name = name[:_find_qualifier_index(name)] return name.strip(' ') -def extract_namespace(name): - # find the namespace the named class lives in, take care of templates - tpl_open = 0 - for pos in xrange(len(name)-1, 1, -1): - c = name[pos] - - # count '<' and '>' to be able to skip template contents - if c == '>': - tpl_open += 1 - elif c == '<': - tpl_open -= 1 - - # collect name up to "::" - elif tpl_open == 0 and c == ':' and name[pos-1] == ':': - # found the extend of the scope ... done - return name[0:pos-1] - - # no namespace; assume outer scope - return "" - #- operator mappings -------------------------------------------------------- _operator_mappings = {} 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 @@ -14,6 +14,7 @@ from pypy.module._cffi_backend import ctypefunc from pypy.module._cppyy import converter, executor, ffitypes, helper +CLASS_FLAGS_IS_PINNED = 0x0001 INSTANCE_FLAGS_PYTHON_OWNS = 0x0001 INSTANCE_FLAGS_IS_REF = 0x0002 @@ -131,7 +132,7 @@ cppclass = space.interp_w(W_CPPClassDecl, w_cppclass) # add back-end specific method pythonizations (doing this on the wrapped # class allows simple aliasing of methods) - capi.pythonize(space, cppclass.name, w_pycppclass) + capi.pythonize(space, w_pycppclass, cppclass.name) state = space.fromcache(State) state.cppclass_registry[rffi.cast(rffi.LONG, cppclass.handle)] = w_pycppclass @@ -816,14 +817,15 @@ class W_CPPScopeDecl(W_Root): - _attrs_ = ['space', 'handle', 'name', 'methods', 'datamembers'] + _attrs_ = ['space', 'handle', 'flags', 'name', 'methods', 'datamembers'] _immutable_fields_ = ['handle', 'name'] def __init__(self, space, opaque_handle, final_scoped_name): self.space = space - self.name = final_scoped_name assert lltype.typeOf(opaque_handle) == capi.C_SCOPE self.handle = opaque_handle + self.flags = 0 + self.name = final_scoped_name self.methods = {} # Do not call "self._build_methods()" here, so that a distinction can # be made between testing for existence (i.e. existence in the cache @@ -1316,7 +1318,7 @@ # cast to actual if requested and possible w_pycppclass = None - if do_cast and rawobject: + if do_cast and rawobject and not (clsdecl.flags & CLASS_FLAGS_IS_PINNED): actual = capi.c_actual_class(space, clsdecl, rawobject) if actual != clsdecl.handle: try: @@ -1390,3 +1392,13 @@ if obj: obj.flags |= INSTANCE_FLAGS_IS_R_VALUE return w_obj + + +# pythonization interface --------------------------------------------------- + +# do not auto-cast to given type +@unwrap_spec(w_pycppclass=W_Root) +def _pin_type(space, w_pycppclass): + w_clsdecl = space.findattr(w_pycppclass, space.newtext("__cppdecl__")) + decl = space.interp_w(W_CPPClassDecl, w_clsdecl) + decl.flags |= CLASS_FLAGS_IS_PINNED 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 @@ -227,7 +227,7 @@ # needs to run first, so that the generic pythonizations can use them import _cppyy _cppyy._register_class(pycls) - _pythonize(pycls) + _pythonize(pycls, pycls.__cppname__) return pycls def make_cpptemplatetype(scope, template_name): @@ -291,6 +291,27 @@ raise AttributeError("'%s' has no attribute '%s'" % (str(scope), name)) +# helper for pythonization API +def extract_namespace(name): + # find the namespace the named class lives in, take care of templates + tpl_open = 0 + for pos in xrange(len(name)-1, 1, -1): + c = name[pos] + + # count '<' and '>' to be able to skip template contents + if c == '>': + tpl_open += 1 + elif c == '<': + tpl_open -= 1 + + # collect name up to "::" + elif tpl_open == 0 and c == ':' and name[pos-1] == ':': + # found the extend of the scope ... done + return name[:pos-1], name[pos+1:] + + # no namespace; assume outer scope + return '', name + # pythonization by decoration (move to their own file?) def python_style_getitem(self, idx): # python-style indexing: check for size and allow indexing from the back @@ -314,15 +335,7 @@ else: return python_style_getitem(self, slice_or_idx) - -_pythonizations = {} -def _pythonize(pyclass): - - try: - _pythonizations[pyclass.__name__](pyclass) - except KeyError: - pass - +def _pythonize(pyclass, name): # general note: use 'in pyclass.__dict__' rather than 'hasattr' to prevent # adding pythonizations multiple times in derived classes @@ -363,10 +376,10 @@ # map begin()/end() protocol to iter protocol on STL(-like) classes, but # not on vector, which is pythonized in the capi (interp-level; there is # also the fallback on the indexed __getitem__, but that is slower) - if not 'vector' in pyclass.__name__[:11] and \ + if not 'vector' in name[:11] and \ ('begin' in pyclass.__dict__ and 'end' in pyclass.__dict__): - if _cppyy._scope_byname(pyclass.__cppname__+'::iterator') or \ - _cppyy._scope_byname(pyclass.__cppname__+'::const_iterator'): + if _cppyy._scope_byname(name+'::iterator') or \ + _cppyy._scope_byname(name+'::const_iterator'): def __iter__(self): i = self.begin() while i != self.end(): @@ -386,7 +399,7 @@ pyclass.__getitem__ = python_style_getitem # string comparisons - if pyclass.__name__ == _cppyy._std_string_name(): + if name == _cppyy._std_string_name(): def eq(self, other): if type(other) == pyclass: return self.c_str() == other.c_str() @@ -396,7 +409,7 @@ pyclass.__str__ = pyclass.c_str # std::pair unpacking through iteration - if 'std::pair' == pyclass.__name__[:9] or 'pair' == pyclass.__name__[:4]: + if 'std::pair' == name[:9]: def getitem(self, idx): if idx == 0: return self.first if idx == 1: return self.second @@ -406,6 +419,16 @@ pyclass.__getitem__ = getitem pyclass.__len__ = return2 + # user provided, custom pythonizations + try: + ns_name, cl_name = extract_namespace(name) + pythonizors = _pythonizations[ns_name] + name = cl_name + except KeyError: + pythonizors = _pythonizations[''] # global scope + + for p in pythonizors: + p(pyclass, name) def _post_import_startup(): # _cppyy should not be loaded at the module level, as that will trigger a @@ -450,11 +473,26 @@ # 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 +_pythonizations = {'' : list()} +def add_pythonization(pythonizor, scope = ''): + """<pythonizor> should be a callable taking two arguments: a class proxy, + and its C++ name. It is called on each time a named class from <scope> + (the global one by default, but a relevant C++ namespace is recommended) + is bound. + """ + if not callable(pythonizor): + raise TypeError("given '%s' object is not callable" % str(pythonizor)) + try: + _pythonizations[scope].append(pythonizor) + except KeyError: + _pythonizations[scope] = list() + _pythonizations[scope].append(pythonizor) + +def remove_pythonization(pythonizor, scope = ''): + """Remove previously registered <pythonizor> from <scope>. + """ + try: + _pythonizations[scope].remove(pythonizor) + return True + except (KeyError, ValueError): + return False diff --git a/pypy/module/_cppyy/test/Makefile b/pypy/module/_cppyy/test/Makefile --- a/pypy/module/_cppyy/test/Makefile +++ b/pypy/module/_cppyy/test/Makefile @@ -7,6 +7,7 @@ fragileDict.so \ operatorsDict.so \ overloadsDict.so \ + pythonizablesDict.so \ stltypesDict.so \ templatesDict.so diff --git a/pypy/module/_cppyy/test/conftest.py b/pypy/module/_cppyy/test/conftest.py --- a/pypy/module/_cppyy/test/conftest.py +++ b/pypy/module/_cppyy/test/conftest.py @@ -10,7 +10,7 @@ import os tst = os.path.basename(item.location[0]) if not tst in ('test_helper.py', 'test_cppyy.py', 'test_pythonify.py', - 'test_datatypes.py'): + 'test_datatypes.py', 'test_pythonization.py'): py.test.skip("genreflex is not installed") import re if tst == 'test_pythonify.py' and \ @@ -19,6 +19,9 @@ elif tst == 'test_datatypes.py' and \ not re.search("AppTestDATATYPES.test0[1-7]", item.location[2]): py.test.skip("genreflex is not installed") + elif tst == 'test_pythonization.py' and \ + not re.search("AppTestPYTHONIZATION.test0[0]", item.location[2]): + py.test.skip("genreflex is not installed") def pytest_ignore_collect(path, config): if py.path.local.sysfind('genreflex') is None and config.option.runappdirect: diff --git a/pypy/module/_cppyy/test/test_helper.py b/pypy/module/_cppyy/test/test_helper.py --- a/pypy/module/_cppyy/test/test_helper.py +++ b/pypy/module/_cppyy/test/test_helper.py @@ -53,11 +53,12 @@ def test_namespace_extraction(): - assert helper.extract_namespace("vector") == "" - assert helper.extract_namespace("std::vector") == "std" - assert helper.extract_namespace("std::vector<double>") == "std" - assert helper.extract_namespace("std::vector<std::vector>") == "std" - assert helper.extract_namespace("vector<double>") == "" - assert helper.extract_namespace("vector<std::vector>") == "" - assert helper.extract_namespace("aap::noot::mies::zus") == "aap::noot::mies" + from pypy.module._cppyy import pythonify + assert pythonify.extract_namespace("vector")[0] == "" + assert pythonify.extract_namespace("std::vector")[0] == "std" + assert pythonify.extract_namespace("std::vector<double>")[0] == "std" + assert pythonify.extract_namespace("std::vector<std::vector>")[0] == "std" + assert pythonify.extract_namespace("vector<double>")[0] == "" + assert pythonify.extract_namespace("vector<std::vector>")[0] == "" + assert pythonify.extract_namespace("aap::noot::mies::zus")[0] == "aap::noot::mies" 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 @@ -378,13 +378,13 @@ import _cppyy - def example01a_pythonize(pyclass): - assert pyclass.__name__ == 'example01a' - def getitem(self, idx): - return self.addDataToInt(idx) - pyclass.__getitem__ = getitem + def example01a_pythonize(pyclass, name): + if name == 'example01a': + def getitem(self, idx): + return self.addDataToInt(idx) + pyclass.__getitem__ = getitem - _cppyy.add_pythonization('example01a', example01a_pythonize) + _cppyy.add_pythonization(example01a_pythonize) e = _cppyy.gbl.example01a(1) _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit