Author: Wim Lavrijsen <[email protected]>
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
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit