[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: Octave as a module for Python
From: |
David Grundberg |
Subject: |
Re: Octave as a module for Python |
Date: |
Wed, 06 May 2009 14:34:51 +0200 |
User-agent: |
Thunderbird 2.0.0.21 (X11/20090318) |
Jaroslav Hajek skrev:
On Tue, May 5, 2009 at 9:32 PM, David Grundberg <address@hidden> wrote:
Announcing Pytave
Hi David,
I have wrapped also the "eval" function into pytave - see the attached
patch (created using bzr diff). How should I go about it? Should
contributions be reported as bugs using the launchpad tracker? Sorry I
have no experience with neither bazaar nor launchpad.
summary:
"eval" is provided as a wrapper to eval_string (parse.h). In
principle, this could be achieved more simply using feval("eval", but
the advantages are:
1. faster (avoids double call and double conversion of code string)
2. explicit control of printing rather than implicitly with nargout (as in eval)
3. a separate exception class for parse error
regards
Hi Jaroslav,
Glad to see you improving my software! As your patch is of a manageable
size, you might as well open a bug report and attach your patch.
I have some comments
1. As the code stands, it is not possible to catch pytave.ParseError.
([module pytave has] "no attribute 'ParseError'") In order to be
correctly declared, the octave_parse_exception will have to be returned
by the _pytave::get_exceptions function and correctly assigned to a
symbol named ParseError in pytave.py.
2. It's unnecessary to duplicate the value conversion documentation, I
think it'll be easier to maintain if it just referred to feval.
I'm attaching a modified patch as a suggestion. I modified the semantics
of the nargout parameter since 0 is valid input.
Furthermore, I see that the builtin eval function accepts a second code
string, CATCH. What are the purpose of this argument, and why did you
not include it?
sincerely,
David
=== modified file 'exceptions.cc'
--- exceptions.cc 2008-10-18 10:28:00 +0000
+++ exceptions.cc 2009-05-06 11:42:53 +0000
@@ -25,6 +25,7 @@
PyObject *octave_error_exception::excclass = NULL;
PyObject *value_convert_exception::excclass = NULL;
PyObject *object_convert_exception::excclass = NULL;
+ PyObject *octave_parse_exception::excclass = NULL;
}
=== modified file 'exceptions.h'
--- exceptions.h 2009-05-03 18:24:48 +0000
+++ exceptions.h 2009-05-06 12:13:29 +0000
@@ -55,6 +55,25 @@
};
+ class octave_parse_exception {
+ public:
+ static bool init() {
+ excclass = PyErr_NewException(
+ const_cast<char*>("pytave.ParseError"),
+ PyExc_RuntimeError, NULL);
+ return excclass != NULL;
+ };
+ static void translate_exception(octave_parse_exception const &py_ex) {
+ PyErr_SetString(excclass, py_ex.error.c_str());
+ }
+ static PyObject *excclass;
+
+ octave_parse_exception(std::string err) { error = err; };
+
+ private:
+ std::string error;
+ };
+
class value_convert_exception {
public:
static bool init() {
=== modified file 'package/pytave.py'
--- package/pytave.py 2009-05-03 18:50:25 +0000
+++ package/pytave.py 2009-05-06 12:24:27 +0000
@@ -22,16 +22,16 @@
import _pytave
_pytave.init()
-(OctaveError, ValueConvertError, ObjectConvertError) \
+(OctaveError, ValueConvertError, ObjectConvertError, ParseError) \
= _pytave.get_exceptions();
def feval(nargout, funcname, *arguments):
"""Executes an Octave function called funcname.
- The function is set to return nargout values. Returned values are
- stored in a tuple. If the nargout argument is less than or equal
- to 0, an empty tuple is returned.
+ The function is set to return nargout values. Returned values
+ are stored in a tuple. If the nargout argument is less than 0,
+ an empty tuple is returned.
M-files are searched for in the Octave path.
@@ -96,6 +96,41 @@
return _pytave.feval(nargout, funcname, arguments)
+def eval(nargout, code, silent=True):
+
+ """Executes a given Octave code.
+
+ The expression is expected to return nargout values. Returned
+ values are stored in a tuple. If the nargout argument is less
+ than 0, an empty tuple is returned.
+
+ All normal scope and function search rules apply. If silent is
+ true (default), the result is not auto-printed, as if a
+ semicolon was appended. Otherwise, auto-printing is enabled.
+
+ See also the Octave documentation for the builtin Octave
+ function eval.
+
+ For information about returned value conversion, see
+ pytave.feval.
+
+ Errors
+ ******
+
+ If the code cannot be parsed, a pytave.ParseError exception
+ occurs.
+
+ Octave runtime errors are encapsulated into pytave.OctaveError
+ exceptions, base class RuntimeError.
+
+ If the resulting values cannot be converted, a
+ pytave.ValueConvertError is raised. This exception inherits
+ TypeError.
+
+ """
+
+ return _pytave.eval(nargout, code, silent)
+
def addpath(*arguments):
"""See Octave documentation"""
return _pytave.feval(1, "addpath", arguments)[0]
=== modified file 'pytave.cc'
--- pytave.cc 2009-05-03 18:24:48 +0000
+++ pytave.cc 2009-05-06 12:23:34 +0000
@@ -48,7 +48,8 @@
if (!octave_error_exception::init()
|| !value_convert_exception::init()
- || !object_convert_exception::init()) {
+ || !object_convert_exception::init()
+ || !octave_parse_exception::init()) {
PyErr_SetString(PyExc_ImportError, "_pytave: init failed");
return;
}
@@ -74,11 +75,45 @@
object(handle<PyObject>(
value_convert_exception::excclass)),
object(handle<PyObject>(
- object_convert_exception::excclass)));
- }
-
+ object_convert_exception::excclass)),
+ object(handle<PyObject>(
+ octave_parse_exception::excclass)));
+ }
+
+ string make_error_message (const Octave_map& map) {
+ ostringstream exceptionmsg;
+ string message = map.stringfield("message", "");
+ string identifier = map.stringfield("identifier", "");
+ Cell stackCell = map.contents("stack");
+
+ // Trim trailing new lines
+ message = message.substr(0, message.find_last_not_of("\r\n") + 1);
+
+ if (!stackCell.is_empty() && stackCell(0).is_map()) {
+ // The struct element is called "stack" but only contain
+ // info about the top frame.
+ Octave_map stack = stackCell(0).map_value();
+ string file = stack.stringfield("file", "");
+ string name = stack.stringfield("name", "");
+ int line = stack.intfield("line", 1);
+ int column = stack.intfield("column", 2);
+
+ exceptionmsg << file << ":" << line << ":" << column << ": ";
+ if (!name.empty())
+ exceptionmsg << "in '" << name << "': ";
+ }
+
+ if (!identifier.empty()) {
+ exceptionmsg << "(identifier: " << identifier << ") ";
+ }
+ exceptionmsg << message;
+
+ return exceptionmsg.str ();
+ }
+
+
boost::python::tuple func_eval(const int nargout,
- const std::string &funcname,
+ const string &funcname,
const boost::python::tuple &arguments) {
octave_value_list octave_args, retval;
@@ -89,7 +124,7 @@
buffer_error_messages++;
Py_BEGIN_ALLOW_THREADS
- retval = feval(funcname, octave_args, nargout);
+ retval = feval(funcname, octave_args, (nargout >= 0) ? nargout : 0);
Py_END_ALLOW_THREADS
if (error_state != 0) {
@@ -102,45 +137,63 @@
octave_value_list lasterror = eval_string("lasterror",
true, parse_status, 1);
if (!lasterror.empty() && lasterror(0).is_map()) {
- ostringstream exceptionmsg;
- Octave_map map = lasterror(0).map_value();
- string message = map.stringfield("message", "");
- string identifier = map.stringfield("identifier", "");
- Cell stackCell = map.contents("stack");
-
- // Trim trailing new lines
- message = message.substr(0, message.find_last_not_of("\r\n") + 1);
-
- if (!stackCell.is_empty() && stackCell(0).is_map()) {
- // The struct element is called "stack" but only contain
- // info about the top frame.
- Octave_map stack = stackCell(0).map_value();
- string file = stack.stringfield("file", "");
- string name = stack.stringfield("name", "");
- int line = stack.intfield("line", 1);
- int column = stack.intfield("column", 2);
-
- exceptionmsg << file << ":" << line << ":" << column << ": ";
- if (!name.empty())
- exceptionmsg << "in '" << name << "': ";
- }
-
- if (!identifier.empty()) {
- exceptionmsg << "(identifier: " << identifier << ") ";
- }
- exceptionmsg << message;
-
- throw octave_error_exception(exceptionmsg.str());
- } else
- throw octave_error_exception("No Octave error available");
- }
-
- if (nargout > 0) {
- boost::python::tuple pytuple;
- octlist_to_pytuple(pytuple, retval);
- return pytuple;
- } else {
- // Return () if nargout <= 0.
+ string exceptionmsg = make_error_message(lasterror(0).map_value
());
+ throw octave_error_exception(exceptionmsg);
+ } else
+ throw octave_error_exception("No Octave error available");
+ }
+
+ if (nargout >= 0) {
+ boost::python::tuple pytuple;
+ octlist_to_pytuple(pytuple, retval);
+ return pytuple;
+ } else {
+ // Return () if nargout < 0.
+ return make_tuple();
+ }
+ }
+
+ boost::python::tuple str_eval(int nargout,
+ const string &code,
+ bool silent) {
+
+ octave_value_list retval;
+ int parse_status;
+
+ reset_error_handler();
+ buffer_error_messages++;
+
+ Py_BEGIN_ALLOW_THREADS
+ retval = eval_string(code, silent, parse_status,
+ (nargout >= 0) ? nargout : 0);
+ Py_END_ALLOW_THREADS
+
+ if (parse_status != 0 || error_state != 0) {
+// error_state values:
+// -2 error without traceback
+// -1 traceback
+// 1 general error
+ int parse_status1 = 0;
+ reset_error_handler();
+ octave_value_list lasterror = eval_string("lasterror",
+ true, parse_status1, 1);
+ if (!lasterror.empty() && lasterror(0).is_map()) {
+ string exceptionmsg = make_error_message (lasterror(0).map_value
());
+
+ if (parse_status != 0)
+ throw octave_parse_exception(exceptionmsg);
+ else
+ throw octave_error_exception(exceptionmsg);
+ } else
+ throw octave_error_exception("No Octave error available");
+ }
+
+ if (nargout >= 0) {
+ boost::python::tuple pytuple;
+ octlist_to_pytuple(pytuple, retval);
+ return pytuple;
+ } else {
+ // Return () if nargout < 0.
return make_tuple();
}
}
@@ -151,6 +204,7 @@
def("init", pytave::init);
def("feval", pytave::func_eval);
+ def("eval", pytave::str_eval);
def("get_exceptions", pytave::get_exceptions);
register_exception_translator<pytave::pytave_exception>(
@@ -159,6 +213,9 @@
register_exception_translator<pytave::octave_error_exception>(
pytave::octave_error_exception::translate_exception);
+ register_exception_translator<pytave::octave_parse_exception>(
+ pytave::octave_parse_exception::translate_exception);
+
register_exception_translator<pytave::object_convert_exception>(
pytave::object_convert_exception::translate_exception);
=== modified file 'test/test.py'
--- test/test.py 2009-05-05 18:57:24 +0000
+++ test/test.py 2009-05-06 12:26:38 +0000
@@ -86,12 +86,28 @@
except Exception, e:
print "FAIL", (value,), e
+def testparseerror(*value):
+ try:
+ print pytave.eval(*value);
+ print "FAIL:", (value,)
+ except pytave.ParseError:
+ pass
+ except Exception, e:
+ print "FAIL", (value,), e
+
def testvalueok(*value):
try:
pytave.feval(1, *value);
except Exception, e:
print "FAIL", (value,), e
+def testevalexpect(numargout, code, expectations):
+ try:
+ results = pytave.eval(numargout, code);
+ if results != expectations:
+ print "FAIL: eval: ", code, " because", results, " !=
", expectations, ","
+ except Exception, e:
+ print "FAIL: eval:", code, ":", e
def testcellinvariant(value):
pass
@@ -200,4 +216,7 @@
if result.shape != (3, 1):
print "FAIL: expected 3x1 matrix"
-
+testparseerror(1, "endfunction")
+testevalexpect(1, "2 + 2", (4,))
+testevalexpect(0, "{2}", ([2],))
+testevalexpect(2, "struct('foo', 2)", ({'foo': [2]},))