3
0
mirror of https://github.com/triqs/dft_tools synced 2024-12-24 13:23:37 +01:00

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.
This commit is contained in:
Olivier Parcollet 2014-07-02 16:32:38 +02:00
parent 7040e9f934
commit 3b2372c437
3 changed files with 105 additions and 51 deletions

View File

@ -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)

View File

@ -510,13 +510,32 @@ template<typename T>
%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<typename T>
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
}

View File

@ -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>(int size)", doc = "DOC of print_a")
module.add_function ("std::vector<int> make_vector(int size)", doc = "DOC of print_a")
module.add_function (name = "make_vector2", signature = "std::vector<std::vector<int>>(int size)", doc = "DOC ....")
module.add_function (name = "vector_x2", signature = "std::vector<int>(std::vector<int> v)", doc = "DOC of print_a")