From 3b2372c4377c55db0fc4ee8dcf65c8506c1b5f5b Mon Sep 17 00:00:00 2001 From: Olivier Parcollet Date: Wed, 2 Jul 2014 16:32:38 +0200 Subject: [PATCH] python wrapper. Pre/postcall can now be inlined - Add the possibility to give a function "on the fly" for the precall and postcall of a python wrapped functoin. - No change for previous code, it is a simple new feature. - changed test accordingly. See my_module_desc.py for an example. --- pytriqs/wrap_generator/wrap_generator.py | 80 ++++++++++++------------ pytriqs/wrap_generator/wrapper.mako.cpp | 58 ++++++++++++++--- test/pytriqs/wrap_test/my_module_desc.py | 18 +++++- 3 files changed, 105 insertions(+), 51 deletions(-) diff --git a/pytriqs/wrap_generator/wrap_generator.py b/pytriqs/wrap_generator/wrap_generator.py index 71bfc78d..a3e435be 100644 --- a/pytriqs/wrap_generator/wrap_generator.py +++ b/pytriqs/wrap_generator/wrap_generator.py @@ -162,44 +162,6 @@ class cfunction : doc = "\n".join([ " " + x.strip() for x in self.doc.split('\n')]) return "Signature : %s\n%s"%( self._get_signature(),doc) -class pyfunction : - """ - Representation of one python function of the extension - It is basically : - - a python name - - a list of overload - - possibly some preprocessing/postprocessing python code. - """ - def __init__(self, name, arity = None, is_method = False, is_static = False, doc = '', python_precall = None, python_postcall = None): - """ - - name : name given in Python - - arity : arity of the function - - is_method : boolean - - is_static : boolean. Is is a static method - - doc : the doc string. - - overloads : a list of cfunction objects representing the various C++ overloads of the function - - python_precall : a python function_ to be called before the call of the C++ function - The function must take F(*args, **kw) and return (args, kw) - - python_postcall : a python function_ to be called after the call of the C++ function - The function must take a python object, and return one... - """ - self.py_name =name # name given in python - self.arity = arity - self.is_method = is_method # can be a method, a function... - self.is_static = is_static # - self.doc = doc - self.python_precall, self.python_postcall = python_precall, python_postcall - self.overloads = [] # List of all C++ overloads - self.do_implement = True # in some cases, we do not want to implement it automatically, (special methods). - self.is_constructor = False - - def add_overload(self, **kw) : - self.overloads.append(cfunction(**kw)) - - def _generate_doc(self) : - s = "\n".join([self.doc, "\n"] + [f._generate_doc() for f in self.overloads]) - return repr(s)[1:-1] # remove the ' ' made by repr - class pure_pyfunction_from_module : """ Representation of one python function defined in Python code in an external module. @@ -235,6 +197,46 @@ class python_function: self.code = "\n".join(['"%s\\n"'%line.rstrip().replace('"', '\\"') for line in ins.getsourcelines(self.f)[0]]) self.doc = f.__doc__ # UNUSED AT THE MOMENT ??? REALLY ??? +class pyfunction : + """ + Representation of one python function of the extension + It is basically : + - a python name + - a list of overload + - possibly some preprocessing/postprocessing python code. + """ + def __init__(self, name, arity = None, is_method = False, is_static = False, doc = '', python_precall = None, python_postcall = None): + """ + - name : name given in Python + - arity : arity of the function + - is_method : boolean + - is_static : boolean. Is is a static method + - doc : the doc string. + - overloads : a list of cfunction objects representing the various C++ overloads of the function + - python_precall : a python function_ to be called before the call of the C++ function + The function must take F(*args, **kw) and return (args, kw) + - python_postcall : a python function_ to be called after the call of the C++ function + The function must take a python object, and return one... + """ + self.py_name =name # name given in python + self.arity = arity + self.is_method = is_method # can be a method, a function... + self.is_static = is_static # + self.doc = doc + def analyse(f): + return python_function(f.__name__, f) if callable(f) else f + self.python_precall, self.python_postcall = analyse(python_precall), analyse(python_postcall) + self.overloads = [] # List of all C++ overloads + self.do_implement = True # in some cases, we do not want to implement it automatically, (special methods). + self.is_constructor = False + + def add_overload(self, **kw) : + self.overloads.append(cfunction(**kw)) + + def _generate_doc(self) : + s = "\n".join([self.doc, "\n"] + [f._generate_doc() for f in self.overloads]) + return repr(s)[1:-1] # remove the ' ' made by repr + def _is_type_a_view(c_type) : return c_type.split('<', 1)[0].endswith("_view") # A bit basic ? @@ -734,7 +736,7 @@ class module_ : def _generate_wrapper_code(self, mako_template, wrap_file) : self._prepare_for_generation() tpl = Template(filename=mako_template) - rendered = tpl.render(module=self, regular_type_if_view_else_type= _regular_type_if_view_else_type, is_type_a_view = _is_type_a_view) + rendered = tpl.render(module=self, regular_type_if_view_else_type= _regular_type_if_view_else_type, is_type_a_view = _is_type_a_view, python_function = python_function) with open(wrap_file,'w') as f: f.write(rendered) diff --git a/pytriqs/wrap_generator/wrapper.mako.cpp b/pytriqs/wrap_generator/wrapper.mako.cpp index f0de5c80..705188f6 100644 --- a/pytriqs/wrap_generator/wrapper.mako.cpp +++ b/pytriqs/wrap_generator/wrapper.mako.cpp @@ -510,13 +510,32 @@ template %if py_meth.python_precall : pyref res_py; // the intermediate result of the pretreatment - // get the module where the function is - static pyref module = pyref::module("${py_meth.python_precall.rsplit('.',1)[0]}"); - if (module.is_null()) goto error_return; - // get the function (only once) - static pyref py_fnt = module.attr("${py_meth.python_precall.rsplit('.',1)[1]}"); - // call the function : return must be a tuple args, kw to replace the current args, kw - res_py = PyObject_Call(py_fnt, args, keywds); + + %if isinstance(py_meth.python_precall,python_function) : + + /// A precall function where the code is given + static const char * _precall_code = ${py_meth.python_precall.code}; + static pyref _precall_py_fnt, _precall_py_dict; // keep the function, contained in a specific local dict + if (_precall_py_fnt.is_null()) { // first init + PyObject* main_module = PyImport_AddModule("__main__"); //borrowed + PyObject* global_dict = PyModule_GetDict(main_module); //borrowed + _precall_py_dict = PyDict_New(); // new ref + if (!PyRun_String(_precall_code, Py_file_input, global_dict, _precall_py_dict)) goto error_return; + _precall_py_fnt = borrowed(PyDict_GetItemString(_precall_py_dict,"${py_meth.python_precall.name}")); + } + + %else: + + // get the module where the function is + static pyref module = pyref::module("${py_meth.python_precall.rsplit('.',1)[0]}"); + if (module.is_null()) goto error_return; + // get the function (only once) + static pyref _precall_py_fnt = module.attr("${py_meth.python_precall.rsplit('.',1)[1]}"); + // call the function : return must be a tuple args, kw to replace the current args, kw + + %endif + + res_py = PyObject_Call(_precall_py_fnt, args, keywds); if (PyErr_Occurred()) goto error_return; if (!PyTuple_Check(res_py) || (PyTuple_Size(res_py)!=2)) { PyErr_SetString(PyExc_RuntimeError,"${py_meth.python_precall} must return a tuple (args,kw) "); goto error_return; } @@ -613,10 +632,29 @@ template post_treatment: %if py_meth.python_postcall : - if (py_result) { // should always be true + if (py_result) { // should always be true + + %if isinstance(py_meth.python_postcall,python_function) : + + /// A postcall function where the code is given + static const char * _postcall_code = ${py_meth.python_postcall.code}; + static pyref _postcall_py_fnt, _postcall_py_dict; // keep the function, contained in a specific local dict + if (_postcall_py_fnt.is_null()) { // first init + PyObject* main_module = PyImport_AddModule("__main__"); //borrowed + PyObject* global_dict = PyModule_GetDict(main_module); //borrowed + _postcall_py_dict = PyDict_New(); // new ref + if (!PyRun_String(_postcall_code, Py_file_input, global_dict, _postcall_py_dict)) goto error_return; + _postcall_py_fnt = borrowed(PyDict_GetItemString(_postcall_py_dict,"${py_meth.python_postcall.name}")); + } + + %else: + static pyref module2 = pyref::module("${py_meth.python_postcall.rsplit('.',1)[0]}"); - static pyref py_fnt2 = module.attr("${py_meth.python_postcall.rsplit('.',1)[1]}"); - PyObject * res_final = PyObject_CallFunctionObjArgs(py_fnt2, py_result, NULL); + static pyref _postcall_py_fnt = module2.attr("${py_meth.python_postcall.rsplit('.',1)[1]}"); + + %endif + + PyObject * res_final = PyObject_CallFunctionObjArgs(_postcall_py_fnt, py_result, NULL); Py_XDECREF(py_result); py_result = res_final; // stealing the ref } diff --git a/test/pytriqs/wrap_test/my_module_desc.py b/test/pytriqs/wrap_test/my_module_desc.py index f0840ed6..510464da 100644 --- a/test/pytriqs/wrap_test/my_module_desc.py +++ b/test/pytriqs/wrap_test/my_module_desc.py @@ -25,7 +25,21 @@ g.add_method(name = "m1", c_name = "m1", signature = "double (int u)", doc = "DO g.add_method(name = "m1", c_name = "m2", signature = "double (double u)", doc = "DOC of m1...") # another version of the method, with some pre/post processing written in python -g.add_method(name = "m1p", c_name = "m1", signature = "double (int u, double y = 3)", doc = "DOC of mm", python_precall = "aux.ffg", python_postcall = "aux.post1") + +def ffg( *args, **kw) : + """ my doc of ffg in module """ + print "calling ffg, with :" + print args + print kw + return tuple(2*x for x in args), kw + +def post1(res) : + #print "calling post1 inline" + return [res] + +# First version uses the external module version, second inline +#g.add_method(name = "m1p", c_name = "m1", signature = "double (int u, double y = 3)", doc = "DOC of mm", python_precall = "aux.ffg", python_postcall = "aux.post1") +g.add_method(name = "m1p", c_name = "m1", signature = "double (int u, double y = 3)", doc = "DOC of mm", python_precall = ffg, python_postcall = post1) # demo of adding a simple piece of C++ code, there is no C++ method corresponding g.add_method(name = "m1_x", calling_pattern = "bool result = (self_c.x >0) && (self_c.x < 10)" , signature = "bool()", doc = "A method which did not exist in C++") @@ -80,7 +94,7 @@ module.add_class(g) # various module functions.... module.add_function (name = "print_a", signature = "void(A a)", doc = "DOC of print_a") module.add_function (name = "print_err", signature = "void(A a)", doc = "DOC of print_a") -module.add_function (name = "make_vector", signature = "std::vector(int size)", doc = "DOC of print_a") +module.add_function ("std::vector make_vector(int size)", doc = "DOC of print_a") module.add_function (name = "make_vector2", signature = "std::vector>(int size)", doc = "DOC ....") module.add_function (name = "vector_x2", signature = "std::vector(std::vector v)", doc = "DOC of print_a")