Mercurial > pytave
view @pyobject/subsref.m @ 411:3613ffbd52b2
Overhaul implicit conversion of arguments and return values
* oct-py-types.cc, oct-py-types.h (pytave::py_implicitly_convert_argument,
pytave::py_implicitly_convert_return_value): New functions.
* __py_struct_from_dict__.cc, oct-py-eval.cc, pycall.cc, pyeval.cc, pyexec.cc:
Use them instead of legacy conversion functions. Add necessary #includes,
remove #includes of legacy header files.
* @pyobject/subsasgn.m, @pyobject/subsref.m: Change %!tests that depend on
NumPy implicit conversion into %!xtests.
* octave_to_python.cc, octave_to_python.h, python_to_octave.cc,
python_to_octave.h: Delete, no longer used.
* Makefile.am (COMMON_SOURCE_FILES, PYTAVE_HEADER_FILES): Remove the files.
author | Mike Miller <mtmiller@octave.org> |
---|---|
date | Wed, 03 May 2017 16:30:45 -0700 |
parents | a8102b1a57a1 |
children |
line wrap: on
line source
## 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 ## @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 varargout = subsref (x, idx) t = idx(1); switch (t.type) case "." assert (ischar (t.subs)) r = pycall ("getattr", x, t.subs); case "()" ## Determine the types and protocols that we are able to index into x_is_callable = __py_isinstance__ (x, "py.collections.Callable"); x_is_sequence = __py_isinstance__ (x, "py.collections.Sequence") ... | __py_isinstance__ (x, "py.array.array") ... | __py_isinstance__ (x, "py.numpy.ndarray"); if (! (x_is_callable || x_is_sequence)) error ("subsref: cannot index Python object, not sequence or callable"); endif if (x_is_sequence) error ("subsref: slice indexing of Python objects not yet implemented"); endif r = pycall (x, t.subs{:}); case "{}" ## Determine the types and protocols that we are able to index into x_is_mapping = __py_isinstance__ (x, "py.collections.Mapping"); x_is_sequence = __py_isinstance__ (x, "py.collections.Sequence") ... | __py_isinstance__ (x, "py.array.array") ... | __py_isinstance__ (x, "py.numpy.ndarray"); if (! (x_is_mapping || x_is_sequence)) error ("subsref: cannot index Python object, not sequence or mapping"); endif ## Subtract one from index: do this for lists, arrays, numpy arrays, etc for i = 1:length (t.subs) j = t.subs{i}; if (isindex (j) && isnumeric (j) && x_is_sequence) t.subs{i} = cast (j, class (sizemax ())) - 1; endif endfor if (ischar (t.subs{1}) && strcmp (t.subs{1}, ":")) if (x_is_mapping) ind = ":"; else ind = int32 ([1:length(x)] - 1); endif elseif (isscalar (t.subs)) ind = t.subs{1}; else ind = pycall ("tuple", t.subs); endif if (isempty (ind) && x_is_sequence) r = pyobject (); elseif (isnumeric (ind) && length (ind) > 1) r = {}; for k = 1:length (ind) r(end+1) = pycall ("operator.getitem", x, ind(k)); endfor else r = pycall ("operator.getitem", x, ind); endif 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)); endif ## unpack results, ensure "ans" works (see also pycall) if (nargout == 0 && ! __py_is_none__ (r)) varargout{1} = r; elseif (nargout == 1) varargout{1} = r; elseif (nargout >= 2) assert (length (r) == nargout, ... "pyobject/subsref: number of outputs must match") [varargout{1:nargout}] = subsref (r, struct ("type", "{}", "subs", {{1:nargout}})); endif endfunction %!test %! % list indexing %! L = pyeval ("[10., 20.]"); %! assert (L{1}, 10) %! assert (L{2}, 20) %!test %! % list indexing, slice %! L = pyeval ("[10., 20., [30., 40.]]"); %! 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 %! L = pyeval ("[1., 2., [10., 11., 12.]]"); %! assert (L{2}, 2) %! assert (L{3}{1}, 10) %! 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 (char (c), "Octave") ## Test that depends on implicit creation of NumPy arrays, do we want this? %!xtest %! % 2D array indexing %! A = pyobject ([1. 2.; 3. 4.]); %! assert (A{1, 1}, 1) %! assert (A{2, 1}, 3) %! assert (A{1, 2}, 2) ## Test element indexing on array.array types %!test %! a = pycall ("array.array", "d", {11, 12, 13, 14}); %! assert (a{1}, 11) %! assert (a{2}, 12) %! assert (a{end}, 14) %!test %! % dict: str key access %! d = pyeval ("{'one':1., 5:5, 6:6}"); %! assert (d{"one"}, 1) %!test %! % dict: integer key access %! d = pyeval ("{5:42., 6:42.}"); %! assert (d{6}, 42) %!test %! % dict: integer key should not subtract one %! d = pyeval ("{5:40., 6:42.}"); %! assert (d{6}, 42) %!test %! % dict: floating point keys should work %! d = pyeval ("{5.5:'ok'}"); %! assert (char (d{5.5}), "ok") %!test %! % dict: make sure key ":" doesn't break anything %! d = pyeval ("{'a':1., ':':2.}"); %! assert (d{'a'}, 1) %! assert (d{':'}, 2) %!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 (isa (ver, "pyobject")) %! assert (ischar (char (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) %!test %! % multiple return values: can get all of them %! f = pyeval ("lambda: (1, 2, 3)"); %! a = f (); %! assert (length (a) == 3) %!test %! % multiple return values: separate them %! f = pyeval ("lambda: (1., 2., 3.)"); %! [a, b, c] = f (); %! assert (a, 1) %! assert (b, 2) %! assert (c, 3) %!test %! % multiple return values: set ans %! f = pyeval ("lambda: (1, 2, 3)"); %! f (); %! assert (length (ans) == 3) %!test %! % ensure None is returned if nargout > 0 %! L = pyeval ("[1, None, 3]"); %! a = L{2}; %! assert (char (a), "None") ## Test of multi-element indexing, fails to return correct number of output args %!xtest %! a = {1, 2, 3, 4, 5, 6}; %! b = pyobject (a); %! b{:}; %! assert (ans, a{end}) %!xtest %! a = {1, 2, 3, 4, 5, 6}; %! b = pyobject (a); %! c = {b{:}}; %! assert (c, a) %!error <cannot index Python object> %! f = pyeval ("abs"); %! f{1} %!error <outputs must match> %! % multiple return values: too many outputs %! f = pyeval ("lambda: (1, 2)"); %! [a, b, c] = f (); %!error <outputs must match> %! % multiple return values: not enough outputs %! f = pyeval ("lambda: (1, 2, 3)"); %! [a, b] = f ();