Mercurial > pytave
view package/pytave.py @ 320:c2aa34730dc9
Convert scalar struct into Python dict (fixes issue #57)
* oct-py-types.cc, oct-py-types.h: New files for conversion of objects between
Octave and Python APIs.
* Makefile.am (COMMON_SOURCE_FILES, PYTAVE_HEADER_FILES): Include new files.
* octave_to_python.cc (pytave::octvalue_to_pyobj): Add case to handle scalar
struct instead of struct array.
* @pyobject/pyobject.m: Add %!tests for pyobject from scalar struct and %!error
tests for failure with struct array.
author | Mike Miller <mtmiller@octave.org> |
---|---|
date | Thu, 11 Aug 2016 16:12:35 -0700 |
parents | a3dc9d24ae38 |
children |
line wrap: on
line source
# -*- coding:utf-8 -*- # # Copyright (C) 2015-2016 Mike Miller # Copyright (C) 2008 David Grundberg, HÃ¥kan Fors Nilsson # Copyright (C) 2009 Jaroslav Hajek, VZLU Prague # # 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. # # Pytave 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 Pytave; see the file COPYING. If not, see # <http://www.gnu.org/licenses/>. """Python to Octave bridge""" import _pytave import atexit import numpy import sys try: from collections.abc import MutableMapping except: from UserDict import DictMixin as MutableMapping arg0 = sys.argv[0] # Some web application packages, such as mod_wsgi for Apache, # completely restrict access to stdin, including an isatty() query. # Hence, if an error occurs, we'll stay safe. try: interactive = sys.stdin.isatty() and (arg0 == '' or arg0 == '-') except IOError: interactive = False _pytave.init(interactive) (OctaveError, ValueConvertError, ObjectConvertError, ParseError, VarNameError) = _pytave.get_exceptions() def _atexit(): _pytave.atexit() atexit.register(_atexit) def feval(nargout, funcname, *arguments): """Executes an Octave function called funcname. The function is set to return nargout values. Returned values are stored in a tuple. If the nargout argument is less than 0, an empty tuple is returned. M-files are searched for in the Octave path. See also the Octave documentation for the builtin Octave function feval. Type conversions **************** The following type conversions are supported: Python to Octave ================ Objects: int (32-bit) int32 float (64-bit) double str character array dict struct list cell array NumPy Array: UBYTE, SBYTE, matrix of correct type USHORT, SHORT, -''- UINT, SINT, -''- LONG, -''- DOUBLE -''- CHAR character array OBJECT cell array All other objects causes a pytave.ObjectConvertError to be raised. This exception inherits TypeError. When dicts are converted, all keys must be strings and constitute valid Octave identifiers. The behavior is analogical to the Octave "struct" function: values that evaluate to cells must have matching dimensions, singleton cells and non-cell values are expanded. Octave to Python ================ All scalar values are regarded as 1x1 matrices, as they are in Octave. Matrix values to NumPy arrays: double DOUBLE single FLOAT logical DOUBLE int64 LONG int32, uint32 INT, UINT int16, uint16 SHORT, USHORT int8, unint8 SBYTE, UBYTE char CHAR cell OBJECT Structs are converted to dicts, where each value is an OBJECT array. All other values causes a pytave.ValueConvertError to be raised. This exception inherits TypeError. Errors ****** Octave runtime errors are encapsulated into pytave.OctaveError exceptions, base class RuntimeError. """ return _pytave.feval(nargout, funcname, arguments) def eval(nargout, code, silent=True): """Executes a given Octave code. The expression is expected to return nargout values. Returned values are stored in a tuple. If the nargout argument is less than 0, an empty tuple is returned. All normal scope and function search rules apply. If silent is true (default), the result is not auto-printed, as if a semicolon was appended. Otherwise, auto-printing is enabled. See also the Octave documentation for the builtin Octave function eval. For information about returned value conversion, see pytave.feval. Errors ****** If the code cannot be parsed, a pytave.ParseError exception occurs. Octave runtime errors are encapsulated into pytave.OctaveError exceptions, base class RuntimeError. If the resulting values cannot be converted, a pytave.ValueConvertError is raised. This exception inherits TypeError. """ return _pytave.eval(nargout, code, silent) def stripdict(dictarray): """A helper function to convert structures obtained from Octave. Because in Octave, all structs are also arrays, they are returned as dicts of object arrays. In the common case of a 1x1 struct, stripdict strips the values.""" sdict = {} for key in dictarray: sdict[key] = dictarray[key][0, 0] return sdict def narrowlist(objarray): """A helper function to convert cell arrays obtained from Octave. Octave cells are returned as NumPy object arrays. This function will flatten the array and convert it into a 1D list.""" return numpy.ravel(objarray).tolist() def simplify(obj): """A helper function to convert results obtained from Octave. This will convert all 1x1 arrays to scalars, vectors to 1D arrays, 1xN and 0x0 character arrays to strings, 1xN, Nx1 and 0x0 cell arrays to lists, and strip scalar dicts. It will work recursively.""" def vectordims(dims, column_allowed=True): return (len(dims) == 2 and ((dims[0] == 1 or (column_allowed and dims[1] == 1)) or (dims[0] == 0 and dims[1] == 0))) if isinstance(obj, numpy.ndarray): tc = obj.dtype.char if tc == 'O': if vectordims(numpy.shape(obj)): return map(simplify, narrowlist(obj)) elif tc == 'c': if vectordims(numpy.shape(obj), False): return obj.tostring() else: dims = numpy.shape(obj) if dims == (1, 1): return obj[0, 0] elif vectordims(dims): return numpy.ravel(obj) elif isinstance(obj, dict): sobj = {} for key in obj: sval = simplify(obj[key]) if isinstance(sval, list) and len(sval) == 1: sval = sval[0] sobj[key] = sval return sobj elif isinstance(obj, tuple): return tuple(map(simplify, obj)) return obj def addpath(*arguments): """See Octave documentation""" return _pytave.feval(1, "addpath", arguments)[0] def rmpath(*paths): """See Octave documentation""" return _pytave.feval(1, "rmpath", paths)[0] def path(*paths): """See Octave documentation""" return _pytave.feval(1, "path", paths)[0] def load_package(pkg_name): """Equivalent to pkg load. See Octave documentation.""" return _pytave.feval(0, "pkg", ("load", pkg_name)) def unload_package(pkg_name): """Equivalent to pkg unload. See Octave documentation.""" return _pytave.feval(0, "pkg", ("unload", pkg_name)) class _VariablesDict(MutableMapping): def __init__(self, global_variables, native=False): self.global_variables = global_variables self.native = native def __getitem__(self, name): if not isinstance(name, basestring): raise TypeError('Expected a string, not a ' + repr(type(name))) try: return _pytave.getvar(name, self.global_variables) except VarNameError: raise KeyError('No Octave variable named ' + name) def __setitem__(self, name, value): if not isinstance(name, basestring): raise TypeError('Expected a string, not a ' + repr(type(name))) _pytave.setvar(name, value, self.global_variables) def __contains__(self, name): if not isinstance(name, basestring): raise TypeError('Expected a string, not a ' + repr(type(name))) return _pytave.isvar(name, self.global_variables) def __delitem__(self, name): if not isinstance(name, basestring): raise TypeError('Expected a string, not a ' + repr(type(name))) # Octave does not gripe when clearing non-existent # variables. To be consistent with Python dict # behavior, we shall do so. if self.__contains__(name): _pytave.delvar(name, self.global_variables) else: raise KeyError('No Octave variable named ' + name) locals = _VariablesDict(global_variables=False) globals = _VariablesDict(global_variables=True) def push_scope(): """Creates a new anonymous local variable scope on the Octave call stack and sets it as the current Octave scope. Subsequent eval, getvar and setvar calls will affect variables within this scope. This is useful to do if you call the Octave engine from within multiple Python functions, to prevent them from hampering each other's data. As such, it is advisable to always create a local scope in a production code. """ return _pytave.push_scope() def pop_scope(): """Pops the current active scope (created previously by push_scope) off the Octave call stack. The previous scope will become the active scope. If already at the top-level scope, this function does nothing. """ _pytave.pop_scope() class _LocalScope: def __init__(self, func): self.func = func self.__name__ = func.__name__ self.__doc__ = func.__doc__ def __call__(self, *args, **kwargs): try: _pytave.push_scope() return self.func(*args, **kwargs) finally: _pytave.pop_scope() def local_scope(func): """Decorates a function to use local Octave scope. Example: @pytave.local_scope def myfunc(a, b): <function body> is equivalent to: def myfunc(a, b): try: pytave.push_scope() <function body> finally: pytave.pop_scope() """ return _LocalScope(func)