changeset 243:cc046496e173

Merged in macdonald/pytave (pull request #22) pyobject: support callables with arguments
author Mike Miller <mike@mtmxr.com>
date Wed, 27 Jul 2016 15:51:48 -0700
parents c64435b14a6e (current diff) f37d7854135c (diff)
children 46382ade33e4
files @pyobject/subsref.m
diffstat 2 files changed, 124 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/@pyobject/subsref.m	Mon Jul 25 17:14:29 2016 -0700
+++ b/@pyobject/subsref.m	Wed Jul 27 15:51:48 2016 -0700
@@ -32,43 +32,53 @@
 ## @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")
+function [varargout] = subsref (x, idx)
+
+  t = idx(1);
+  switch t.type
+    case "()"
+      r = pycall (x, t.subs{:});
+
+    case "."
+      assert (ischar (t.subs))
+      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")
         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));
+      endfor
+      s = ["[" strjoin(subsstrs, ", ") "]"];
+      ## XXX: can we use .__getitem__ here?
+      r = pyeval (sprintf ("__InOct__['%s']%s", x.id, s));
+
+    otherwise
+      t
+      error ("@pyobject/subsref: not implemented")
+  endswitch
+
+  ## deal with additional indexing (might be recursive)
+  if (length (idx) > 1)
+    r = subsref (r, idx(2:end));
+  end
+
+  ## unpack results, ensure "ans" works (see also pycall)
+  isNone = pyeval ("lambda x: x is None");
+  if (nargout > 0 || ! pycall (isNone, r))
+    varargout{1} = r;
+    assert (nargout <= 1)
+  end
 endfunction
 
 
@@ -123,3 +133,48 @@
 %! pyexec ("d = {5:40, 6:42}")
 %! d = pyobject.fromPythonVarName ("d");
 %! assert (d{6}, 42)
+
+%!test
+%! % method call with args
+%! s = pyeval ("set({1, 2})");
+%! s.add (42)
+%! assert (length (s) == 3)
+
+%!test
+%! % get a callable
+%! s = pyeval ("set({1, 2})");
+%! sa = s.add;
+%! assert (isa (sa, "pyobject"))
+%! % and then call it
+%! sa (42)
+%! assert (length (s) == 3)
+
+%!test
+%! % callable can return something
+%! s = pyeval ("set({1, 2})");
+%! v = s.pop ();
+%! assert (length (s) == 1)
+%! assert (v == 1 || v == 2)
+
+%!test
+%! % chain
+%! pyexec ("import sys")
+%! s = pyeval ("set({sys})");
+%! ver = s.pop ().version;
+%! assert (ischar (ver))
+
+%!test
+%! % don't set "ans" if no return value
+%! s = pyeval ("set({1, 2})");
+%! sa = s.add;
+%! clear ans
+%! sa (42)
+%! assert (! exist ("ans", "var"))
+
+%!test
+%! % *do* set "ans" if return value
+%! s = pyeval ("set({1, 2})");
+%! clear ans
+%! s.pop ();
+%! assert (exist ("ans", "var"))
+%! assert (length (s) == 1)
--- a/pycall.cc	Mon Jul 25 17:14:29 2016 -0700
+++ b/pycall.cc	Wed Jul 27 15:51:48 2016 -0700
@@ -56,6 +56,23 @@
   @result{} 1.4142\n\
 @end group\n\
 @end example\n\
+\n\
+If the callable has no return, and an lvalue is specified, it will be set\n\
+to @code{None}.  However, if no lvalue was specified, @code{ans} will not\n\
+be set.  For example:\n\
+@example\n\
+@group\n\
+s = pyeval (\"set([1, 2])\");\n\
+pycall (s.add, 3)\n\
+\n\
+r = pycall (s.add, 4)\n\
+  @result{} r = [pyobject ...]\n\
+\n\
+      None\n\
+\n\
+@end group\n\
+@end example\n\
+\n\
 @seealso{pyeval, pyexec}\n\
 @end deftypefn")
 {
@@ -191,7 +208,8 @@
       object idtmp = hex_function (id_function (res));
       id = extract<std::string> (idtmp);
 
-      if (! res.is_none ())
+      // Ensure reasonable "ans" behaviour, consistent with Python's "_".
+      if (nargout > 0 || ! res.is_none ())
         {
           octave_value val;
           pytave::pyobj_to_octvalue (val, res);
@@ -280,4 +298,18 @@
 %!error <NameError>
 %! pyexec ("def raiseException ():\n  raise NameError ('oops')")
 %! pycall ("raiseException")
+
+## None as a return value
+%!test
+%! f = pyeval ("lambda: None");
+%! r = pycall (f);
+%! isNone = pyeval("lambda a: a is None");
+%! assert (isNone (r))
+
+## But returning None will not set "ans"
+%!test
+%! f = pyeval ("lambda: None");
+%! clear ans
+%! pycall (f);
+%! assert (! exist ("ans", "var"))
 */