changeset 309:3e0decdf59b0

Merged in genuinelucifer/pytave_main (pull request #24) Refactor code and add option to specify namespace for pyexec and pyeval (fixes issue #25)
author Mike Miller <mtmiller@octave.org>
date Tue, 09 Aug 2016 15:25:37 -0700
parents 616ec5f18d95 (diff) b4e56f7255f7 (current diff)
children 94617b6a6554
files pycall.cc
diffstat 7 files changed, 314 insertions(+), 80 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/@pyobject/cell.m	Tue Aug 09 15:25:37 2016 -0700
@@ -0,0 +1,84 @@
+## 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 <http://www.gnu.org/licenses/>.
+
+## -*- texinfo -*-
+## @documentencoding UTF-8
+## @defmethod @@pyobject cell (@var{x})
+## Convert an iterable Python object to a cell array.
+##
+## For example, by default Python lists are not automatically
+## converted into native Octave objects:
+## @example
+## @group
+## L = pyeval ("[10, 20, 'hello']")
+##   @result{} L = [pyobject ...]
+##
+##       [10, 20, 'hello']
+## @end group
+## @end example
+##
+## However, we can convert the list to a cell array:
+## @example
+## @group
+## C = cell (L)
+##   @result{} C =
+##     @{
+##       [1,1] =  10
+##       [1,2] =  20
+##       [1,3] = hello
+##     @}
+## @end group
+## @end example
+##
+## The conversion is not recursive, in the following sense:
+## @example
+## @group
+## L = pyeval ("[10, 20, [33, 44], 50]");
+## C = cell (L)
+##   @result{} C =
+##     @{
+##       [1,1] =  10
+##       [1,2] =  20
+##             = [pyobject ...]
+##
+##                 [33, 44]
+##
+##       [1,4] =  50
+##     @}
+## @end group
+## @end example
+##
+## @seealso{cell2mat}
+## @end defmethod
+
+
+function c = cell (x)
+  ## FIXME: subsref should take care of this case
+  if (length (x) == 0)
+    c = cell (0, 1);
+    return
+  endif
+  c = subsref (x, struct ("type", "{}", "subs", {{":"}}));
+endfunction
+
+
+%!test
+%! L = pyeval ("(1, 2, 3)");
+%! C = cell (L);
+%! assert (iscell (C))
+%! assert (C, {1, 2, 3})
--- a/@pyobject/fieldnames.m	Tue Aug 09 11:28:15 2016 -0700
+++ b/@pyobject/fieldnames.m	Tue Aug 09 15:25:37 2016 -0700
@@ -53,14 +53,8 @@
                  " and not a.startswith('_')]"]);
 
   names_obj = pycall (cmd, x);
-  ## FIXME: names = cellfun (@char, cell (names_obj), "uniformoutput", false);
-  len = length (names_obj);
-  names = cell (len, 1);
-  if (len > 0)
-    idx = struct ("type", "{}", "subs", {{1:len}});
-    [names{1:len}] = subsref (names_obj, idx);
-    names = cellfun (@char, names, "uniformoutput", false);
-  endif
+  names = cellfun (@char, cell (names_obj), "uniformoutput", false);
+  names = names(:);
 
 endfunction
 
--- a/@pyobject/methods.m	Tue Aug 09 11:28:15 2016 -0700
+++ b/@pyobject/methods.m	Tue Aug 09 15:25:37 2016 -0700
@@ -55,8 +55,7 @@
 ## @group
 ## methods pyobject
 ##   @print{} Methods for class pyobject:
-##   @print{} display  ...  subsref
-##   @print{} ...
+##   @print{} ...   display   ...
 ## @comment this doctest may need updating as we add methods
 ## @end group
 ## @end example
@@ -73,14 +72,7 @@
 
   mtds_list_obj = pycall (cmd, x);
 
-  ## FIXME: mtds_list = cellfun (@char, cell (mtds_list_obj), "uniformoutput", false);
-  len = length (mtds_list_obj);
-  mtds_list = cell (len, 1);
-  if (len > 0)
-    idx = struct ("type", "{}", "subs", {{1:len}});
-    [mtds_list{1:len}] = subsref (mtds_list_obj, idx);
-    mtds_list = cellfun (@char, mtds_list, "uniformoutput", false);
-  endif
+  mtds_list = cellfun (@char, cell (mtds_list_obj), "uniformoutput", false);
 
   if (nargout == 0)
     ## FIXME: should this be available as @pyobject/ismodule.m ?
--- a/@pyobject/pyobject.m	Tue Aug 09 11:28:15 2016 -0700
+++ b/@pyobject/pyobject.m	Tue Aug 09 15:25:37 2016 -0700
@@ -170,6 +170,67 @@
         len = 1;
       end_try_catch
     endfunction
+
+
+    function [n, varargout] = size (x, d)
+      assert (nargin <= 2)
+      try
+        idx = struct ("type", ".", "subs", "shape");
+        sz = subsref (x, idx);
+        sz = cell2mat (cell (sz));
+      catch
+        ## if it had no shape, make it a row vector
+        sz = [1 length(x)];
+      end_try_catch
+
+      ## simplest case
+      if (nargout <= 1 && nargin == 1)
+        n = sz;
+        return
+      endif
+
+      ## quirk: pad extra dimensions with ones
+      if (nargin < 2)
+        d = 1;
+      endif
+      sz(end+1:max (d,nargout-end)) = 1;
+
+      if (nargin > 1)
+        assert (nargout <= 1)
+        n = sz(d);
+        return
+      endif
+
+      ## multiple outputs
+      n = sz(1);
+      for i = 2:(nargout-1)
+        varargout{i-1} = sz(i);
+      endfor
+      ## last is product of all remaining
+      varargout{nargout-1} = prod (sz(nargout:end));
+    endfunction
+
+
+    function n = numel (x)
+      assert (nargin == 1)
+      sz = size (x);
+      n = prod (sz);
+    endfunction
+
+    function n = ndims (x)
+      assert (nargin == 1)
+      n = length (size (x));
+    endfunction
+
+    function r = end (x, index_pos, num_indices)
+      assert (nargin == 3)
+      assert (isscalar (index_pos))
+      if (num_indices == 1)
+        r = numel (x);
+      else
+        r = size (x, index_pos);
+      endif
+    endfunction
   endmethods
 endclassdef
 
@@ -189,6 +250,55 @@
 %! pyobj = pyeval ("sys");
 %! assert (length (pyobj), 1)
 
+%!assert (size (pyeval ("[10, 20, 30]")), [1 3])
+%!assert (size (pyeval ("[10, 20, 30]"), 1), 1)
+%!assert (size (pyeval ("[10, 20, 30]"), 2), 3)
+%!assert (size (pyeval ("[10, 20, 30]"), 3), 1)
+
+%!test
+%! L = pyeval ("[10, 20, 30]");
+%! a = size (L);
+%! assert (a, [1, 3])
+%! [a b] = size (L);
+%! assert ([a b], [1 3])
+%! [a b c] = size (L);
+%! assert ([a b c], [1 3 1])
+
+%!assert (numel (pyeval ("[10, 20, 30]")), 3)
+
+%!test
+%! L = pyeval ("[10, 20, 30]");
+%! assert (L{end}, 30)
+%! assert (L{end-1}, 20)
+
+%!test
+%! % ensure "end" works for iterables that are not lists
+%! myrange = pyeval ( ...
+%!   "range if __import__('sys').hexversion >= 0x03000000 else xrange");
+%! R = pycall (myrange, int32 (5), int32 (10), int32 (2));
+%! assert (R{end}, 9)
+
+%!shared a
+%! pyexec ("class _myclass(): shape = (3, 4, 5)")
+%! a = pyeval ("_myclass()");
+%!assert (size (a), [3 4 5])
+%!assert (size (a, 3), 5)
+%!test
+%! s = size (a);
+%! assert (s, [3 4 5])
+%!test
+%! [n m] = size (a);
+%! assert ([n m], [3 20])
+%!test
+%! [n m o] = size (a);
+%! assert ([n m o], [3 4 5])
+%!test
+%! [n m o p] = size (a);
+%! assert ([n m o p], [3 4 5 1])
+%!assert (numel (a), 60)
+%!assert (ndims (a), 3)
+%!shared
+
 %!assert (char (pyeval ("None")), "None")
 %!assert (char (pyeval ("'this is a string'")), "this is a string")
 %!assert (char (pyeval ("[1, 2, 3, 4, 5]")), "[1, 2, 3, 4, 5]")
--- a/@pyobject/subsref.m	Tue Aug 09 11:28:15 2016 -0700
+++ b/@pyobject/subsref.m	Tue Aug 09 15:25:37 2016 -0700
@@ -123,7 +123,7 @@
 %! % list indexing, slice
 %! pyexec ("L = [10, 20, [30, 40]]")
 %! L = pyobject.fromPythonVarName ("L");
-%! [L2{1:length(L)}] = L{:};
+%! L2 = L{:};
 %! assert (L2{1}, 10)
 %! assert (L2{2}, 20)
 %! assert (L2{3}{1}, 30)
@@ -138,6 +138,14 @@
 %! assert (L{3}{3}, 12)
 
 %!test
+%! % list indexing, assign to vars
+%! L = pyeval ("[1, 2, 'Octave']");
+%! [a, b, c] = L{:};
+%! assert (a, 1)
+%! assert (b, 2)
+%! assert (c, "Octave")
+
+%!test
 %! % 2D array indexing
 %! pyexec ("import numpy")
 %! pyexec ("A = numpy.array([[1, 2], [3, 4]])")
--- a/octave_to_python.cc	Tue Aug 09 11:28:15 2016 -0700
+++ b/octave_to_python.cc	Tue Aug 09 15:25:37 2016 -0700
@@ -181,8 +181,6 @@
       return create_array<bool, boolNDArray> (matrix.bool_array_value (), NPY_BOOL);
     if (matrix.is_string ())
       return create_array<char, charNDArray> (matrix.char_array_value (), NPY_CHAR);
-    if (matrix.is_cell ())
-      return create_array<PyObject *, Cell> (matrix.cell_value (), NPY_OBJECT);
 
     throw value_convert_exception ("Octave matrix type not known, conversion not implemented");
   }
@@ -196,6 +194,26 @@
   }
 
   static void
+  octcell_to_pyobject (boost::python::object& py_object,
+                       const Cell& cell)
+  {
+    if (! (cell.is_empty () || cell.is_vector ()))
+      throw value_convert_exception (
+        "unable to convert multidimensional cell array into Python sequence");
+
+    boost::python::list sequence;
+
+    for (octave_idx_type i = 0; i < cell.numel (); i++)
+      {
+        boost::python::object py_val;
+        octvalue_to_pyobj (py_val, cell(i));
+        sequence.append (py_val);
+      }
+
+    py_object = sequence;
+  }
+
+  static void
   octmap_to_pyobject (boost::python::object& py_object,
                       const octave_map& map)
   {
@@ -211,6 +229,42 @@
       }
   }
 
+  inline PyObject *
+  python_integer_value (int32_t value)
+  {
+#if PY_VERSION_HEX >= 0x03000000
+    return PyLong_FromLong (value);
+#else
+    return PyInt_FromLong (value);
+#endif
+  }
+
+  inline PyObject *
+  python_integer_value (uint32_t value)
+  {
+    return PyLong_FromUnsignedLong (value);
+  }
+
+  inline PyObject *
+  python_integer_value (int64_t value)
+  {
+#if (defined (HAVE_LONG_LONG) && (SIZEOF_LONG_LONG > SIZEOF_LONG))
+    return PyLong_FromLongLong (value);
+#else
+    return PyLong_FromLong (value);
+#endif
+  }
+
+  inline PyObject *
+  python_integer_value (uint64_t value)
+  {
+#if (defined (HAVE_LONG_LONG) && (SIZEOF_LONG_LONG > SIZEOF_LONG))
+    return PyLong_FromUnsignedLongLong (value);
+#else
+    return PyLong_FromUnsignedLong (value);
+#endif
+  }
+
   static void
   octscalar_to_pyobject (boost::python::object& py_object,
                          const octave_value& arg)
@@ -224,22 +278,22 @@
       obj = PyFloat_FromDouble (arg.double_value ());
 
     else if (arg.is_int8_type ())
-      obj = PyLong_FromLong (arg.int8_scalar_value ().value ());
+      obj = python_integer_value (arg.int8_scalar_value ().value ());
     else if (arg.is_int16_type ())
-      obj = PyLong_FromLong (arg.int16_scalar_value ().value ());
+      obj = python_integer_value (arg.int16_scalar_value ().value ());
     else if (arg.is_int32_type ())
-      obj = PyLong_FromLong (arg.int32_scalar_value ().value ());
+      obj = python_integer_value (arg.int32_scalar_value ().value ());
     else if (arg.is_int64_type ())
-      obj = PyLong_FromLong (arg.int64_scalar_value ().value ());
+      obj = python_integer_value (arg.int64_scalar_value ().value ());
 
     else if (arg.is_uint8_type ())
-      obj = PyLong_FromUnsignedLong (arg.uint8_scalar_value ().value ());
+      obj = python_integer_value (arg.uint8_scalar_value ().value ());
     else if (arg.is_uint16_type ())
-      obj = PyLong_FromUnsignedLong (arg.uint16_scalar_value ().value ());
+      obj = python_integer_value (arg.uint16_scalar_value ().value ());
     else if (arg.is_uint32_type ())
-      obj = PyLong_FromUnsignedLong (arg.uint32_scalar_value ().value ());
+      obj = python_integer_value (arg.uint32_scalar_value ().value ());
     else if (arg.is_uint64_type ())
-      obj = PyLong_FromUnsignedLong (arg.uint64_scalar_value ().value ());
+      obj = python_integer_value (arg.uint64_scalar_value ().value ());
 
     else if (arg.is_bool_type ())
       obj = PyBool_FromLong (arg.bool_value ());
@@ -273,8 +327,10 @@
       octstring_to_pyobject (py_object, octvalue.string_value ());
     else if (octvalue.is_scalar_type ())
       octscalar_to_pyobject (py_object, octvalue);
+    else if (octvalue.is_cell ())
+      octcell_to_pyobject (py_object, octvalue.cell_value ());
     else if (octvalue.is_numeric_type () || octvalue.is_string ()
-             || octvalue.is_cell () || octvalue.is_bool_type ())
+             || octvalue.is_bool_type ())
       octvalue_to_pyarr (py_object, octvalue);
     else if (octvalue.is_map ())
       octmap_to_pyobject (py_object, octvalue.map_value ());
--- a/pycall.cc	Tue Aug 09 11:28:15 2016 -0700
+++ b/pycall.cc	Tue Aug 09 15:25:37 2016 -0700
@@ -78,7 +78,6 @@
 @end deftypefn")
 {
   octave_value_list retval;
-  object res;
   std::string id;
 
   int nargin = args.length ();
@@ -102,61 +101,18 @@
       if (callable.is_none ())
         error("pycall: FUNC must be a string or a Python reference");
 
-      std::vector<object> pyargs;
+      PyObject *pyargs = PyTuple_New (nargin - 1);
       for (int i = 1; i < nargin; i++)
         {
           object arg;
           pytave::octvalue_to_pyobj (arg, args(i));
-          pyargs.push_back (arg);
+          PyObject *obj = arg.ptr ();
+          Py_INCREF (obj);
+          PyTuple_SET_ITEM (pyargs, i - 1, obj);
         }
 
-      switch (nargin - 1)
-        {
-        case 0:
-          res = callable ();
-          break;
-        case 1:
-          res = callable (pyargs[0]);
-          break;
-        case 2:
-          res = callable (pyargs[0], pyargs[1]);
-          break;
-        case 3:
-          res = callable (pyargs[0], pyargs[1], pyargs[2]);
-          break;
-        case 4:
-          res = callable (pyargs[0], pyargs[1], pyargs[2], pyargs[3]);
-          break;
-        case 5:
-          res = callable (pyargs[0], pyargs[1], pyargs[2], pyargs[3],
-                          pyargs[4]);
-          break;
-        case 6:
-          res = callable (pyargs[0], pyargs[1], pyargs[2], pyargs[3],
-                          pyargs[4], pyargs[5]);
-          break;
-        case 7:
-          res = callable (pyargs[0], pyargs[1], pyargs[2], pyargs[3],
-                          pyargs[4], pyargs[5], pyargs[6]);
-          break;
-        case 8:
-          res = callable (pyargs[0], pyargs[1], pyargs[2], pyargs[3],
-                          pyargs[4], pyargs[5], pyargs[6], pyargs[7]);
-          break;
-        case 9:
-          res = callable (pyargs[0], pyargs[1], pyargs[2], pyargs[3],
-                          pyargs[4], pyargs[5], pyargs[6], pyargs[7],
-                          pyargs[8]);
-          break;
-        case 10:
-          res = callable (pyargs[0], pyargs[1], pyargs[2], pyargs[3],
-                          pyargs[4], pyargs[5], pyargs[6], pyargs[7],
-                          pyargs[8], pyargs[9]);
-          break;
-        default:
-          error ("pycall: more than 10 arguments are not yet supported");
-          break;
-        }
+      PyObject *result = PyEval_CallObjectWithKeywords (callable.ptr (), pyargs, 0);
+      object res = object (handle<PyObject> (result));
 
       // Ensure reasonable "ans" behaviour, consistent with Python's "_".
       if (nargout > 0 || ! res.is_none ())
@@ -212,6 +168,18 @@
 %!assert (pycall ("typename", "Hello world"), "str")
 %!assert (pycall ("typename", char ([1, 2, 3])), "str")
 
+## Test construction of sequence types from cell arrays
+%!assert (char (pycall ("list")), "[]")
+%!assert (char (pycall ("list", {})), "[]")
+%!assert (char (pycall ("list", {1, 2, 3})), "[1.0, 2.0, 3.0]")
+%!assert (char (pycall ("list", {int8(1), int8(2), int8(3)})), "[1, 2, 3]")
+%!assert (char (pycall ("tuple")), "()")
+%!assert (char (pycall ("tuple", {})), "()")
+%!assert (char (pycall ("tuple", {1, 2, 3})), "(1.0, 2.0, 3.0)")
+%!assert (char (pycall ("tuple", {int8(1), int8(2), int8(3)})), "(1, 2, 3)")
+%!error (pycall ("list", {1, 2, 3; 4, 5, 6}))
+%!error (pycall ("dict", {1, 2, 3}))
+
 ## Test round trip type preservation / conversion
 %!test
 %! pyexec ("def roundtrip(x): return x");
@@ -221,6 +189,28 @@
 %!   assert (pycall ("roundtrip", values{i}), values{i});
 %! endfor
 
+## Test conversion of integer types into Python
+%!assert (pycall (pyeval ("lambda x: type(x) == type(0) and x ==        0"), int8 (0)))
+%!assert (pycall (pyeval ("lambda x: type(x) == type(0) and x == -2**7   "), intmin ("int8")))
+%!assert (pycall (pyeval ("lambda x: type(x) == type(0) and x ==  2**7 -1"), intmax ("int8")))
+%!assert (pycall (pyeval ("lambda x: type(x) == type(0) and x ==        0"), intmin ("uint8")))
+%!assert (pycall (pyeval ("lambda x: type(x) == type(0) and x ==  2**8 -1"), intmax ("uint8")))
+%!assert (pycall (pyeval ("lambda x: type(x) == type(0) and x ==        0"), int16 (0)))
+%!assert (pycall (pyeval ("lambda x: type(x) == type(0) and x == -2**15  "), intmin ("int16")))
+%!assert (pycall (pyeval ("lambda x: type(x) == type(0) and x ==  2**15-1"), intmax ("int16")))
+%!assert (pycall (pyeval ("lambda x: type(x) == type(0) and x ==        0"), intmin ("uint16")))
+%!assert (pycall (pyeval ("lambda x: type(x) == type(0) and x ==  2**16-1"), intmax ("uint16")))
+%!assert (pycall (pyeval ("lambda x: type(x) == type(0) and x ==        0"), int32 (0)))
+%!assert (pycall (pyeval ("lambda x: type(x) == type(0) and x == -2**31  "), intmin ("int32")))
+%!assert (pycall (pyeval ("lambda x: type(x) == type(0) and x ==  2**31-1"), intmax ("int32")))
+%!assert (pycall (pyeval ("lambda x: type(x) == type(2**64) and x ==        0"), intmin ("uint32")))
+%!assert (pycall (pyeval ("lambda x: type(x) == type(2**64) and x ==  2**32-1"), intmax ("uint32")))
+%!assert (pycall (pyeval ("lambda x: type(x) == type(2**64) and x ==        0"), int64 (0)))
+%!assert (pycall (pyeval ("lambda x: type(x) == type(2**64) and x == -2**63  "), intmin ("int64")))
+%!assert (pycall (pyeval ("lambda x: type(x) == type(2**64) and x ==  2**63-1"), intmax ("int64")))
+%!assert (pycall (pyeval ("lambda x: type(x) == type(2**64) and x ==        0"), intmin ("uint64")))
+%!assert (pycall (pyeval ("lambda x: type(x) == type(2**64) and x ==  2**64-1"), intmax ("uint64")))
+
 %!error <argument type conversion>
 %! pyexec ("def intwrapper(x): return int(x)");
 %! pycall ("intwrapper", ftp ());