# HG changeset patch # User Mike Miller # Date 1469828367 25200 # Node ID 24faaa3cf5e5fc9636e58c7e1da11c6b56c220eb # Parent ecca6a885ffd7401f124282a9e9e4809f31402c8# Parent decd3bd7da7e57def065a34f8f38d8111984cddb Merged in macdonald/pytave (pull request #23) [wip] partial support for multiple outputs diff -r decd3bd7da7e -r 24faaa3cf5e5 @py/py.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/@py/py.m Fri Jul 29 14:39:27 2016 -0700 @@ -0,0 +1,30 @@ +## Copyright (C) 2016 Mike Miller +## +## This file is part of Pytave. +## +## Pytave is free software; you can redistribute it and/or modify it +## under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or (at +## your option) any later version. +## +## Pytave is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Pytave; see the file COPYING. If not, see +## . + +## -*- texinfo -*- +## @deftypefn {} {} py +## @deftypefnx {} {} py.@var{pyname} +## Get the value of a Python object or call a Python function. +## @end deftypefn + +function p = py () + p = class (struct (), "py"); +endfunction + +%!assert (py.math.sqrt (2), sqrt (2)) +%!assert (ischar (py.sys.version)) diff -r decd3bd7da7e -r 24faaa3cf5e5 @py/subsref.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/@py/subsref.m Fri Jul 29 14:39:27 2016 -0700 @@ -0,0 +1,56 @@ +## Copyright (C) 2016 Mike Miller +## +## This file is part of Pytave. +## +## Pytave is free software; you can redistribute it and/or modify it +## under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 3 of the License, or (at +## your option) any later version. +## +## Pytave is distributed in the hope that it will be useful, but +## WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +## General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with Pytave; see the file COPYING. If not, see +## . + +## -*- texinfo -*- +## @deftypefn {} {} subsref (@var{x}, @var{idx}) +## Implements Python name lookup via dot indexing. +## @enddeftypefn + +function varargout = subsref (x, idx) + + if (nargin != 2) + print_usage (); + endif + + if (! isa (x, "py")) + error ("py: invalid call to subsref"); + endif + + if (isempty (idx) || ! isstruct (idx)) + error ("py: invalid call to subsref without indices"); + endif + + type = idx(1).type; + subs = idx(1).subs; + + if (type != ".") + error ("py: invalid indexing type"); + endif + + y = pycall ("__import__", subs); + + if (numel (idx) > 1) + y = subsref (y, idx(2:end)); + endif + + is_none = pyeval ("lambda x: x is None"); + if (nargout > 0 || ! pycall (is_none, y)) + varargout{1} = y; + endif + +endfunction diff -r decd3bd7da7e -r 24faaa3cf5e5 @pyobject/dummy.m --- a/@pyobject/dummy.m Mon Jul 25 14:04:17 2016 -0700 +++ b/@pyobject/dummy.m Fri Jul 29 14:39:27 2016 -0700 @@ -32,12 +32,17 @@ ## @result{} ans = ## @{ ## [1,1] = bit_length -## [1,2] = conjugate -## [1,3] = denominator -## [1,4] = imag -## [1,5] = numerator -## [1,6] = real -## @} +## [2,1] = conjugate +## @} +## +## sort (fieldnames (g)) +## @result{} ans = +## @{ +## [1,1] = denominator +## [2,1] = imag +## [3,1] = numerator +## [4,1] = real +## @} ## ## g.numerator ## @result{} ans = 6 diff -r decd3bd7da7e -r 24faaa3cf5e5 @pyobject/methods.m --- a/@pyobject/methods.m Mon Jul 25 14:04:17 2016 -0700 +++ b/@pyobject/methods.m Fri Jul 29 14:39:27 2016 -0700 @@ -21,8 +21,8 @@ ## @defmethod @@pyobject methods (@var{x}) ## List the properties/callables of a Python object. ## -## Returns a cell array of strings, the names of the properties -## and ``callables'' of @var{x}. +## Returns a cell array of strings, the names of the ``callables'' +## of @var{x}. ## ## Example: ## @example @@ -30,7 +30,7 @@ ## pyexec ("import os") ## os = pyeval ("os"); ## methods (os) -## @print{} Methods for module os: +## @print{} Methods for Python module 'os': ## @print{} ... ## @print{} chdir ... ## @print{} ... @@ -38,7 +38,7 @@ ## @result{} x = ## @{ ## [1,1] = ... -## [1,2] = ... +## [2,1] = ... ## ... = chdir ## ... = getenv ## ... @@ -46,8 +46,11 @@ ## @end group ## @end example ## +## To get the properties (non-callables) of an object, +## @pxref{@@pyobject/fieldnames}. +## ## Note that if you instead want the methods implemented by -## the Octave class @code{@@pyobject}, use can always do: +## the Octave class @code{@@pyobject}, you can always do: ## @example ## @group ## methods pyobject @@ -58,15 +61,13 @@ ## @end group ## @end example ## -## @seealso{methods} +## @seealso{methods, @@pyobject/fieldnames} ## @end defmethod function mtds = methods (x) - # filter the output of `dir(x)` - # (to get callable methods only: - # [a for a in dir(x) if callable(getattr(x, a)) and not a.startswith('__')] + # filter the output of `dir(x)` to get callable methods only cmd = sprintf (["[a for x in (__InOct__['%s'],) for a in dir(x) " ... " if callable(getattr(x, a))" ... " and not a.startswith('__')]"], @@ -81,17 +82,16 @@ if (pycall (is_module, x)) modulename = pycall ("getattr", x, "__name__"); - printf ("Methods for module %s:\n", modulename); + printf ("Methods for Python module '%s':\n", modulename); else ## FIXME: should be `class (x)` classref = pycall ("getattr", x, "__class__"); classname = pycall ("getattr", classref, "__name__"); - ## FIXME: indicate that this is a Python class? - printf ("Methods for class %s:\n", classname); + printf ("Methods for Python class '%s':\n", classname); endif disp (list_in_columns (mtds_list)); else - mtds = mtds_list; + mtds = mtds_list(:); endif endfunction diff -r decd3bd7da7e -r 24faaa3cf5e5 @pyobject/subsasgn.m --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/@pyobject/subsasgn.m Fri Jul 29 14:39:27 2016 -0700 @@ -0,0 +1,123 @@ +## Copyright (C) 2016 Colin B. Macdonald +## +## This file is part of Pytave. +## +## Pytave is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; either version 3 of the License, +## or (at your option) any later version. +## +## This software is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty +## of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See +## the GNU General Public License for more details. +## +## You should have received a copy of the GNU General Public +## License along with this software; see the file COPYING. +## If not, see . + +## -*- texinfo -*- +## @documentencoding UTF-8 +## @defop Method @@pyobject subsasgn (@var{x}, @var{idx}, @var{rhs}) +## @defopx Operator @@pyobject {@var{x}.@var{property} =} {@var{rhs}} +## @defopx Operator @@pyobject {@var{x}@{@var{i}@} =} {@var{rhs}} +## @defopx Operator @@pyobject {@var{x}@{@var{i}, @var{j}, @dots{}@} =} {@var{rhs}} +## @defopx Operator @@pyobject {@var{x}@{@var{a}@} =} {@var{rhs}} +## Indexed assignment to Python objects. +## +## @seealso{@@pyobject/subsref} +## @end defop + +function r = subsasgn(x, idx, rhs) + + if (nargin != 3) + print_usage (); + endif + + ## If rhs is a pyobject but x is not, dispatch to the builtin subsasgn + if (! isa (x, "pyobject")) + r = builtin ("subsasgn", x, idx, rhs); + return; + endif + + switch idx.type + case "." + assert (ischar (idx.subs)) + pycall ("setattr", x, idx.subs, rhs); + r = x; + + case "{}" + ## XXX: doesn't support slices or anything like that yet + + ## Subtract one from index: do this for lists, numpy arrays, etc + pyexec ("import collections") + pyexec ("import numpy") + x_is_list = pycall (pyeval ( + "lambda x: isinstance(x, (collections.Sequence, numpy.ndarray))"), + x); + for i = 1:length (idx.subs) + j = idx.subs{i}; + if (x_is_list && isindex (j) && isnumeric (j)) + idx.subs{i} = cast (j, class (sizemax ())) - 1; + endif + endfor + + if (isscalar (idx.subs)) + ind = idx.subs{1}; + else + error ("not implemented, waiting on #26, #27") + endif + + xsi = pycall ("getattr", x, "__setitem__"); # x.__setitem__ + pycall (xsi, ind, rhs); + r = x; + + otherwise + idx + rhs + error ("@pyobject/subsasgn: not implemented") + endswitch +endfunction + + +%!test +%! pyexec ("class MyClass: a = 1") +%! t = pyeval ("MyClass()"); +%! t.b = 6; +%! assert (t.b, 6) + +%!test +%! % list indexing +%! pyexec ("L = [10, 20]") +%! L = pyobject.fromPythonVarName ("L"); +%! L{2} = "Octave"; +%! assert (length (L) == 2) +%! assert (L{1}, 10) +%! assert (L{2}, "Octave") + +%!test +%! % dict assignment, adding new keys +%! pyexec ("d = dict()") +%! d = pyobject.fromPythonVarName ("d"); +%! d{"a"} = 3; +%! d{"b"} = 4; +%! assert (d{"a"}, 3) +%! assert (d{"b"}, 4) + +%!test +%! % dict assignment, update existing key +%! pyexec ("d = {'a':1}") +%! d = pyobject.fromPythonVarName ("d"); +%! d{"a"} = 3; +%! assert (d{"a"}, 3) + +%!test +%! % dict assignment, other keys (e.g., Issue #10). +%! pyexec ("d = dict()") +%! d = pyobject.fromPythonVarName ("d"); +%! d{"5"} = 10; +%! d{5.5} = 11; +%! d{5} = 12; +%! assert (d{"5"}, 10) +%! assert (d{5.5}, 11) +%! assert (d{5}, 12) diff -r decd3bd7da7e -r 24faaa3cf5e5 @pyobject/subsref.m --- a/@pyobject/subsref.m Mon Jul 25 14:04:17 2016 -0700 +++ b/@pyobject/subsref.m Fri Jul 29 14:39:27 2016 -0700 @@ -44,24 +44,34 @@ r = pycall ("getattr", x, t.subs); case "{}" - subsstrs = {}; - for j = 1:length (t.subs) - thissub = t.subs{j}; - if (ischar (thissub) && strcmp (thissub, ":")) - subsstrs{j} = ":"; - elseif (ischar (thissub)) - subsstrs{j} = ["'" thissub "'"]; - elseif (isnumeric (thissub) && isscalar (thissub)) - ## note: python indexed from 0 - subsstrs{j} = num2str (thissub - 1); - else - thissub - error ("@pyobject/subsref: subs not supported") + ## Subtract one from index: do this for lists, numpy arrays, etc + pyexec ("import collections") + pyexec ("import numpy") + x_is_list = pycall (pyeval ( + "lambda x: isinstance(x, (collections.Sequence, numpy.ndarray))"), + x); + for i = 1:length(t.subs) + j = t.subs{i}; + if (isindex (j) && isnumeric (j) && x_is_list) + t.subs{i} = cast (j, class (sizemax ())) - 1; endif endfor - s = ["[" strjoin(subsstrs, ", ") "]"]; - ## XXX: can we use .__getitem__ here? - r = pyeval (sprintf ("__InOct__['%s']%s", x.id, s)); + + if (isscalar (t.subs)) + ind = t.subs{1}; + else + ## XXX: after #26, #27, I think its just: + #ind = pycall ("tuple", t.subs); + pyexec (["global _temp\n" ... + "def pystoretemp(x):\n" ... + " global _temp\n" ... + " _temp = x"]); + pycall ("pystoretemp", t.subs); + pyexec ("_temp = tuple(_temp[0])"); + ind = pyobject.fromPythonVarName ("_temp"); + endif + gi = pycall ("getattr", x, "__getitem__"); # x.__getitem__ + r = pycall (gi, ind); otherwise t @@ -94,8 +104,8 @@ %! assert (L{1}, 10) %! assert (L{2}, 20) -%!test -%! % list indexing +%!xtest +%! % list indexing, slice %! pyexec ("L = [10, 20, [30, 40]]") %! L = pyobject.fromPythonVarName ("L"); %! L2 = L{:}; @@ -133,13 +143,26 @@ %! d = pyobject.fromPythonVarName ("d"); %! assert (d{6}, 42) -%!xtest -%! % dict: integer key should not subtract one (FIXME: Issue #10) +%!test +%! % dict: integer key should not subtract one %! pyexec ("d = {5:40, 6:42}") %! d = pyobject.fromPythonVarName ("d"); %! assert (d{6}, 42) %!test +%! % dict: floating point keys should work +%! pyexec ("d = {5.5:'ok'}") +%! d = pyobject.fromPythonVarName ("d"); +%! assert (d{5.5}, "ok") + +%!test +%! % dict: make sure key ":" doesn't break anything +%! pyexec ("d = {'a':1, ':':2}") +%! d = pyobject.fromPythonVarName ("d"); +%! assert (d{'a'}, 1) +%! assert (d{':'}, 2) + +%!test %! % method call with args %! s = pyeval ("set({1, 2})"); %! s.add (42) diff -r decd3bd7da7e -r 24faaa3cf5e5 exceptions.cc --- a/exceptions.cc Mon Jul 25 14:04:17 2016 -0700 +++ b/exceptions.cc Fri Jul 29 14:39:27 2016 -0700 @@ -52,6 +52,7 @@ PyObject *ptype, *pvalue, *ptraceback; PyErr_Fetch (&ptype, &pvalue, &ptraceback); + PyErr_NormalizeException (&ptype, &pvalue, &ptraceback); std::string message; try diff -r decd3bd7da7e -r 24faaa3cf5e5 pycall.cc --- a/pycall.cc Mon Jul 25 14:04:17 2016 -0700 +++ b/pycall.cc Fri Jul 29 14:39:27 2016 -0700 @@ -278,11 +278,11 @@ %! endfor %!error -%! pyexec ("def intwrapper(x):\n return int(x)\n"); +%! pyexec ("def intwrapper(x): return int(x)"); %! pycall ("intwrapper", ftp ()); %!test -%! pyexec ("def pyfunc(x):\n return 2*x"); +%! pyexec ("def pyfunc(x): return 2*x"); %! z = pycall ("pyfunc", [20 20]); %! assert (z, [40 40]) @@ -296,7 +296,7 @@ %! assert (pycall ("pyfunc", 10), 10) %!error -%! pyexec ("def raiseException ():\n raise NameError ('oops')") +%! pyexec ("def raiseException(): raise NameError('oops')") %! pycall ("raiseException") ## None as a return value diff -r decd3bd7da7e -r 24faaa3cf5e5 pyeval.cc --- a/pyeval.cc Mon Jul 25 14:04:17 2016 -0700 +++ b/pyeval.cc Fri Jul 29 14:39:27 2016 -0700 @@ -141,6 +141,10 @@ %!assert (isa (pyeval ("object()"), "pyobject")) +%!assert (isnumeric (pyeval ("__import__('sys').maxsize"))) +%!assert (pyeval ("99999999999999"), 99999999999999) +%!assert (pyeval ("-99999999999999"), -99999999999999) + ## FIXME: these will change when dict, list, and tuple are not converted %!assert (pyeval ("{'x': 1, 'y': 2}"), struct ("x", 1, "y", 2)) %!assert (pyeval ("[1, 2, 3]"), {1, 2, 3}) @@ -155,6 +159,6 @@ %! assert (z{4}{2}{2}, 422) %!error -%! pyexec ("def raiseException ():\n raise NameError ('oops')") -%! pyeval ("raiseException ()") +%! pyexec ("def raiseException(): raise NameError ('oops')") +%! pyeval ("raiseException()") */ diff -r decd3bd7da7e -r 24faaa3cf5e5 python_to_octave.cc --- a/python_to_octave.cc Mon Jul 25 14:04:17 2016 -0700 +++ b/python_to_octave.cc Fri Jul 29 14:39:27 2016 -0700 @@ -477,7 +477,7 @@ void pyobj_to_octvalue (octave_value& oct_value, const boost::python::object& py_object) { - extract intx (py_object); + extract longx (py_object); extract boolx (py_object); extract doublex (py_object); extract complexx (py_object); @@ -490,8 +490,8 @@ if (boolx.check () && PyBool_Check ((PyArrayObject*)py_object.ptr ())) oct_value = boolx (); - else if (intx.check ()) - oct_value = intx (); + else if (longx.check ()) + oct_value = longx (); else if (doublex.check ()) oct_value = doublex (); else if (complexx.check ())