Mercurial > pytave
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 ());