Mercurial > pytave
changeset 225:0c6f7ae8a95b
Merged in macdonald/pytave (pull request #6)
add @pyobject support
author | Mike Miller <mike@mtmxr.com> |
---|---|
date | Wed, 20 Jul 2016 11:12:36 -0700 |
parents | 377f2dc057ea (current diff) 7feece80fbfa (diff) |
children | 382bb1d91239 |
files | Makefile.am octave_to_python.cc pyeval.cc python_to_octave.cc |
diffstat | 7 files changed, 559 insertions(+), 5 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/@pyobject/display.m Wed Jul 20 11:12:36 2016 -0700 @@ -0,0 +1,61 @@ +## Copyright (C) 2016 Colin B. Macdonald +## +## This file is part of PyTave. +## +## OctSymPy 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 display (@var{x}) +## Custom display for pyobjects. +## +## Example: +## @example +## @group +## pyexec('import sys') +## sysmodule = pyeval('sys') +## @result{} sysmodule = [pyobject ...] +## +## <module 'sys' (built-in)> +## +## @end group +## @end example +## +## @seealso{@@pyobject/disp} +## @end defmethod + + +function display (x) + + loose = ! __compactformat__ (); + + printf ("%s = [pyobject %s]\n", inputname (1), getid (x)); + s = disp (x); + s = make_indented (s); + if (loose), printf("\n"); endif + disp (s) + if (loose), printf("\n"); endif + +endfunction + + +function s = make_indented(s, n) + if (nargin == 1) + n = 2; + endif + pad = char (double (" ")*ones (1,n)); + s = strrep (s, "\n", ["\n" pad]); + s = [pad s]; # first line +endfunction
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/@pyobject/dummy.m Wed Jul 20 11:12:36 2016 -0700 @@ -0,0 +1,132 @@ +## Copyright (C) 2016 Colin B. Macdonald +## +## This file is part of PyTave. +## +## OctSymPy 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 dummy (@var{x}) +## Does nothing, stores doctests for now. +## +## +## Simple example: +## @example +## @group +## pyexec ("g = 6") +## g = pyobject.fromPythonVarName ("g"); +## +## sort (methods (g)) +## @result{} ans = +## @{ +## [1,1] = bit_length +## [1,2] = conjugate +## [1,3] = denominator +## [1,4] = imag +## [1,5] = numerator +## [1,6] = real +## @} +## +## g.numerator +## @result{} ans = 6 +## g.denominator +## @result{} ans = 1 +## @end group +## @end example +## +## +## You can delete an object in Python and it will persist: +## @example +## @group +## pyexec ("d = dict(one=1, two=2)") +## x = pyobject.fromPythonVarName ("d") +## @result{} x = [pyobject ...] +## @{'two': 2, 'one': 1@} +## +## # oops, overwrote d in Python: +## pyexec ("d = 42") +## +## # but have no fear, we still have a reference to it: +## x +## @result{} x = [pyobject ...] +## @{'two': 2, 'one': 1@} +## @end group +## @end example +## +## We can accesss ``callables'' (methods) of objects: +## @example +## @group +## x.keys() +## @result{} ans = +## @{ +## [1,1] = two +## [1,2] = one +## @} +## @end group +## @end example +## +## @code{pyeval} returns a @@pyobject for things it cannot convert to +## Octave-native objects: +## @example +## @group +## pyexec ("import sys") +## sysmodule = pyeval ("sys") +## @result{} sysmodule = [pyobject ...] +## <module 'sys' (built-in)> +## @end group +## @end example +## +## After you have the object, you can access its properties: +## @example +## @group +## sysmodule.version +## @result{} ans = ... +## @end group +## @end example +## +## +## TODO: this should return a cell array with a double, a string, +## and an @@pyobject in it: +## @example +## @group +## pyeval ("[42, 'hello', sys]") # doctest: +XFAIL +## @result{} ans = +## @{ +## [1,1] = 42 +## [1,2] = hello +## [1,3] = +## [PyObject id ...] +## <module 'sys' (built-in)> +## @} +## @end group +## @end example +## +## A @@pyobject can be passed back to Python. This does not make +## a copy but rather a reference to the original object. +## For example: +## @example +## @group +## pycall ("__builtin__.print", sysmodule) # doctest: +XFAIL +## @print{} <module 'sys' (built-in)> +## @end group +## @end example +## (FIXME: I think this failure may correspond to an existing doctest issue). +## +## @seealso{pyobject} +## @end defmethod + +function dummy (x) + +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/@pyobject/methods.m Wed Jul 20 11:12:36 2016 -0700 @@ -0,0 +1,83 @@ +## Copyright (C) 2016 Colin B. Macdonald +## +## This file is part of PyTave. +## +## OctSymPy 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 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}. +## +## Example: +## @example +## @group +## pyexec ("import sys") +## sys = pyeval ("sys"); +## methods (sys) +## @result{} ans = +## @{ +## [1,1] = ... +## [1,2] = ... +## ... = path +## ... = version +## ... +## @} +## @end group +## @end example +## +## Note that if you instead want the methods implemented by +## the Octave class @code{@@pyobject}, use can always do: +## @example +## @group +## methods pyobject +## @print{} Methods for class pyobject: +## @print{} display ... subsref +## @comment this doctest may need updating as we add methods +## @end group +## @end example +## +## @seealso{methods} +## @end defmethod + + +function L = methods (x) + # filter the output of `dir(x)` + # (to get properties only: + # [a for a in dir(x) if not callable(getattr(x, a)) and not a.startswith('__')] + cmd = sprintf ( ... + "[a for a in dir(__InOct__['%s']) if not a.startswith('__')]", ... + getid(x)); + # TODO: may need to convert from Python list to Octave list + L = pyeval (cmd); +endfunction + + +%!test +%! pyexec ("import sys") +%! sys = pyeval ("sys"); +%! L = methods (sys); +%! % sys has lots of methods +%! assert (length (L) >= 32) +%! % version is one of them +%! assert (any (strcmp (L, "version"))) + +%!test +%! pyexec ("import sys") +%! L = methods (pyeval ("sys")); +%! assert (iscell (L))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/@pyobject/pyobject.m Wed Jul 20 11:12:36 2016 -0700 @@ -0,0 +1,128 @@ +## Copyright (C) 2016 Colin B. Macdonald +## +## This file is part of PyTave. +## +## OctSymPy 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 +## @defun pyobject (@var{s}) +## Wrap a Python object. +## +## TODO: where/how to document classdef classes? +## +## @seealso{pyexec, pyeval} +## @end defun + + +classdef pyobject < handle + properties + id + end + + methods (Static) + function x = fromPythonVarName (pyvarname) + # Warning: just for testing, may be removed without notice! + # If @var{pyvarname} is a string, its assumed to be a variable + # name, e.g., previously created with pyexec. This must exist + # at the time of construction but it can disappear later (we + # will keep track of the reference). + if (! ischar (pyvarname)) + error("pyobject: currently we only take variable names as input") + endif + cmd = sprintf ([ ... + 'if not ("__InOct__" in vars() or "__InOct__" in globals()):\n' ... + ' __InOct__ = dict()\n' ... + ' # FIXME: make it accessible elsewhere?\n' ... + ' import __main__\n' ... + ' __main__.__InOct__ = __InOct__\n' ... + '__InOct__[hex(id(%s))] = %s' ], ... + pyvarname, pyvarname); + pyexec (cmd); + id = pyeval (["hex(id(" pyvarname "))"]); + x = pyobject (id); + endfunction + endmethods + + + methods + function x = pyobject (id) + % warning: not intended for casual use: you must also insert + % the object into the Python `__InOct__` dict with key `id`. + x.id = id; + endfunction + + function delete (x) + # Called on clear of the last reference---for subclasses of + # handle; not called at all for "value classes". + # + # FIXME: #46497 this is never called! + # Workaround: call @code{delete(x)} right before @code{clear x}. But + # be careful, @code{x} needs to be the last reference: don't do this: + # @example + # d = pyobject (...); + # d2 = d; + # delete (d) + # clear d + # d2 + # @print{} ... KeyError ... + # @end example + + #disp ("delete") + + # throws KeyError if it wasn't in there for some reason + cmd = sprintf ("__InOct__.pop('%s')", x.id); + pyexec (cmd) + endfunction + + # methods defined in external files + dummy (x) + display (x) + subsref (x, idx) + + function r = getid (x) + r = x.id; + endfunction + + function varargout = disp (x) + s = pyeval (sprintf ("str(__InOct__['%s'])", x.id)); + if (nargout == 0) + disp (s) + else + varargout = {s}; + endif + endfunction + + function s = whatclass (x) + s = pyeval (sprintf ("str(__InOct__['%s'].__class__)", x.id)); + endfunction + + function vargout = help (x) + idx = struct ("type", ".", "subs", "__doc__"); + s = subsref (x, idx); + if (nargout == 0) + disp (s) + else + vargout = {s}; + endif + endfunction + endmethods +endclassdef + + +%!test +%! pyexec ("import sys") +%! A = pyeval ("sys"); +%! assert (isa (A, "pyobject"))
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/@pyobject/subsref.m Wed Jul 20 11:12:36 2016 -0700 @@ -0,0 +1,125 @@ +## Copyright (C) 2016 Colin B. Macdonald +## +## This file is part of PyTave. +## +## OctSymPy 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 +## @defop Method @@pyobject subsref (@var{x}, @var{idx}) +## @defopx Operator @@pyobject {@var{x}.@var{property}} {} +## @defopx Operator @@pyobject {@var{x}.@var{method}(@var{a}, @dots)} {} +## @defopx Operator @@pyobject {@var{x}@{@var{i}@}} {} +## @defopx Operator @@pyobject {@var{x}@{@var{i}, @var{j}, @dots@}} {} +## @defopx Operator @@pyobject {@var{x}(@var{a})} {} +## @defopx Operator @@pyobject {@var{x}(@var{a}, @var{b}, @dots)} {} +## Call methods and access properties of a Python object. +## +## +## @seealso{@@pyobject/subsasgn} +## @end defop + + +function r = subsref (x, idx) + s = ""; + for i = 1:length (idx) + t = idx(i); + switch t.type + case "()" + if (! isempty (t.subs)) + t + error("not implemented: function calls with arguments") + endif + s = sprintf ("%s()", s); + case "." + assert (ischar (t.subs)) + s = sprintf ("%s.%s", s, 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") + endif + endfor + s = [s "[" strjoin(subsstrs, ", ") "]"]; + otherwise + t + error("@pyobject/subsref: not implemented") + endswitch + endfor + r = pyeval (sprintf ("__InOct__['%s']%s", x.id, s)); +endfunction + + +%!test +%! % list indexing +%! pyexec ("L = [10, 20]") +%! L = pyobject.fromPythonVarName ("L"); +%! assert (L{1}, 10) +%! assert (L{2}, 20) + +%!test +%! % list indexing +%! pyexec ("L = [10, 20, [30, 40]]") +%! L = pyobject.fromPythonVarName ("L"); +%! L2 = L{:}; +%! assert (L2{1}, 10) +%! assert (L2{2}, 20) +%! assert (L2{3}{1}, 30) +%! assert (L2{3}{2}, 40) + +%!test +%! % list indexing, nested list +%! pyexec ("L = [1, 2, [10, 11, 12]]") +%! L = pyobject.fromPythonVarName ("L"); +%! assert (L{2}, 2) +%! assert (L{3}{1}, 10) +%! assert (L{3}{3}, 12) + +%!test +%! % 2D array indexing +%! pyexec ("import numpy") +%! pyexec ("A = numpy.array([[1, 2], [3, 4]])") +%! A = pyobject.fromPythonVarName ("A"); +%! assert (A{1, 1}, 1) +%! assert (A{2, 1}, 3) +%! assert (A{1, 2}, 2) + +%!test +%! % dict: str key access +%! pyexec ("d = {'one':1, 5:5, 6:6}") +%! d = pyobject.fromPythonVarName ("d"); +%! assert (d{"one"}, 1) + +%!test +%! % dict: integer key access +%! pyexec ("d = {5:42, 6:42}") +%! d = pyobject.fromPythonVarName ("d"); +%! assert (d{6}, 42) + +%!xtest +%! % dict: integer key should not subtract one (FIXME: Issue #10) +%! pyexec ("d = {5:40, 6:42}") +%! d = pyobject.fromPythonVarName ("d"); +%! assert (d{6}, 42)
--- a/octave_to_python.cc Fri Jul 15 00:51:23 2016 -0700 +++ b/octave_to_python.cc Wed Jul 20 11:12:36 2016 -0700 @@ -31,6 +31,7 @@ #include <octave/oct.h> #include <octave/ov.h> #include <octave/oct-map.h> +#include <octave/parse.h> #include <iostream> #include "arrayobjectdefs.h" @@ -277,7 +278,13 @@ octvalue_to_pyarr (py_object, octvalue); else if (octvalue.is_map ()) octmap_to_pyobject (py_object, octvalue.map_value ()); - else + else if (octvalue.is_object ()) { + octave_value_list tmp = feval ("getid", ovl (octvalue), 1); + std::string hexid = tmp(0).string_value(); + //std::cerr << "passed in hexid: " << hexid << std::endl; + // FIXME: added a messy ref to __InOct__ in __main__, find a better way + py_object = boost::python::import("__main__").attr("__InOct__")[hexid]; + } else throw value_convert_exception ( "Conversion from Octave value not implemented"); }
--- a/pyeval.cc Fri Jul 15 00:51:23 2016 -0700 +++ b/pyeval.cc Wed Jul 20 11:12:36 2016 -0700 @@ -29,6 +29,7 @@ #include <boost/python/numeric.hpp> #include <oct.h> +#include <octave/parse.h> #define PYTAVE_DO_DECLARE_SYMBOL #include "arrayobjectdefs.h" @@ -64,13 +65,21 @@ std::string code = args(0).string_value (); + std::string id; + object res; + Py_Initialize (); + object main_module = import ("__main__"); + object main_namespace = main_module.attr ("__dict__"); + try { - object main_module = import ("__main__"); - object main_namespace = main_module.attr ("__dict__"); - object res = eval (code.c_str (), main_namespace, main_namespace); + res = eval (code.c_str (), main_namespace, main_namespace); + object builtins = main_module.attr ("__builtins__"); + // hex(id(res)) + object idtmp = builtins.attr("hex")(builtins.attr("id")(res)); + id = extract<std::string> (idtmp); // FIXME: currently, we cannot return the raw object to octave... if (! res.is_none ()) @@ -82,7 +91,16 @@ } catch (pytave::object_convert_exception const &) { - error ("pyeval: error in return value type conversion"); + // Ensure we have a __InOct__ dict, and then put `res` into it + exec ("if not (\"__InOct__\" in vars() or \"__InOct__\" in globals()):\n" + " __InOct__ = dict()\n" + " # FIXME: make it accessible elsewhere?\n" + " import __main__\n" + " __main__.__InOct__ = __InOct__\n", + main_namespace, main_namespace); + main_namespace["__InOct__"][id] = res; + // Create @pyobject + retval = feval ("pyobject", ovl (id), 1); } catch (error_already_set const &) {