Package mpython
Sub-modules
mpython.array
mpython.cell
mpython.core
mpython.exceptions
mpython.helpers
mpython.matlab_class
mpython.matlab_function
mpython.runtime
mpython.sparse_array
mpython.struct
mpython.utils
Classes
class Array (*args, **kwargs)
-
Expand source code
class Array(_ListishMixin, WrappedArray): """ Numeric array, compatible with matlab arrays. ```python # Instantiate from size Array(N, M, ...) Array([N, M, ...]) Array.from_shape([N, M, ...]) # Instantiate from existing numeric array Array(other_array) Array.from_any(other_array) # Other options Array(..., dtype=None, order=None, *, copy=None, owndata=None) ``` !!! warning Lists or vectors of integers can be interpreted as shapes or as numeric arrays to copy. They are interpreted as shapes by the `Array` constructor. To ensure that they are interpreted as arrays to copy, use `Array.from_any`. """ @classmethod def _DEFAULT(cls, n: list = ()) -> int: return 0 def __new__(cls, *args, **kwargs) -> "Array": mode, arg, kwargs = cls._parse_args(*args, **kwargs) if mode == "shape": obj = super().__new__(cls, shape=arg, **kwargs) obj[...] = cls._DEFAULT() if not issubclass(obj.dtype.type, np.number): raise TypeError("Array data type must be numeric") return obj else: return cls.from_any(arg, **kwargs) def _as_runtime(self) -> np.ndarray: return np.ndarray.view(self, np.ndarray) @classmethod def _from_runtime(cls, other, runtime=None) -> "Array": other = np.asarray(other) if len(other.shape) == 2 and other.shape[0] == 1: other = other.squeeze(0) return np.ndarray.view(other, cls) @classmethod def from_shape(cls, shape=tuple(), **kwargs) -> "Array": """ Build an array of a given shape. Parameters ---------- shape : list[int] Shape of new array. Other Parameters ---------------- dtype : np.dtype | None, default='double' Target data type. order : {"C", "F"} | None, default=None Memory layout. * "C" : row-major (C-style); * "F" : column-major (Fortran-style). Returns ------- array : Array New array. """ # Implement in __new__ so that array owns its data return cls(list(shape), **kwargs) @classmethod def from_any(cls, other, **kwargs) -> "Array": """ Convert an array-like object to a numeric array. Parameters ---------- other : ArrayLike object to convert. Other Parameters ---------------- dtype : np.dtype | None, default=None Target data type. Guessed if `None`. order : {"C", "F", "A", "K"} | None, default=None Memory layout. * `"C"` : row-major (C-style); * `"F"` : column-major (Fortran-style); * `"A"` : (any) `"F"` if a is Fortran contiguous, `"C"` otherwise; * `"K"` : (keep) preserve input order; * `None`: preserve input order if possible, `"C"` otherwise. copy : bool | None, default=None Whether to copy the underlying data. * `True` : the object is copied; * `None` : the the object is copied only if needed; * `False`: raises a `ValueError` if a copy cannot be avoided. owndata : bool, default=None If `True`, ensures that the returned `Array` owns its data. This may trigger an additional copy. Returns ------- array : Array Converted array. """ # prepare for copy owndata = kwargs.pop("owndata", False) copy = None if owndata else kwargs.pop("copy", None) inp = other # ensure array other = np.asanyarray(other, **kwargs) if not issubclass(other.dtype.type, np.number): if kwargs.get("dtype", None): # user-specified non numeric type -> raise raise TypeError("Array data type must be numeric") other = np.asanyarray(other, dtype=np.float64, **kwargs) # view as Array other = np.ndarray.view(other, cls) # copy (after view so that output owns data if copy=True) other = _copy_if_needed(other, inp, copy) # take ownership if owndata: tmp = other other = cls(tmp.shape, strides=tmp.strides) other[...] = tmp return other @classmethod def from_cell(cls, other, **kwargs) -> "Array": """ Convert a `Cell` to a numeric `Array`. Parameters ---------- other : Cell Cell to convert. Other Parameters ---------------- dtype : np.dtype | None, default=None Target data type. Guessed if `None`. order : {"C", "F", "A", "K"} | None, default="K" Memory layout. * `"C"` : row-major (C-style); * `"F"` : column-major (Fortran-style); * `"A"` : (any) `"F"` if a is Fortran contiguous, `"C"` otherwise; * `"K"` : (keep) preserve input order. owndata : bool, default=None If `True`, ensures that the returned `Array` owns its data. This may trigger an additional copy. Returns ------- array : Array Converted array. """ Cell = _imports.Cell if not isinstance(other, Cell): raise TypeError(f"Expected a {Cell} but got a {type(other)}") order = kwargs.get("order", None) if order in (None, "K", "A"): order = ( "F" if other.flags.f_contiguous else "C" if other.flags.c_contiguous else order ) kwargs["order"] = order return cls.from_any(other.tolist(), **kwargs) @property def as_num(self) -> "Array": return self def __repr__(self): if self.ndim == 0: # Scalar -> display as python scalar return np.array2string(self, separator=", ") else: return super().__repr__()
Numeric array, compatible with matlab arrays.
# Instantiate from size Array(N, M, ...) Array([N, M, ...]) Array.from_shape([N, M, ...]) # Instantiate from existing numeric array Array(other_array) Array.from_any(other_array) # Other options Array(..., dtype=None, order=None, *, copy=None, owndata=None)
Warning
Lists or vectors of integers can be interpreted as shapes or as numeric arrays to copy. They are interpreted as shapes by the
Array
constructor. To ensure that they are interpreted as arrays to copy, useArray.from_any()
.Ancestors
- mpython.core.mixin_types._ListishMixin
- WrappedArray
- numpy.ndarray
- AnyWrappedArray
- AnyMatlabArray
- MatlabType
Subclasses
Static methods
def from_any(other, **kwargs) ‑> Array
-
Convert an array-like object to a numeric array.
Parameters
other
:ArrayLike
- object to convert.
Other Parameters
dtype
:np.dtype | None
, default=None
- Target data type. Guessed if
None
. order
:{"C", "F", "A", "K"} | None
, default=None
-
Memory layout.
"C"
: row-major (C-style);"F"
: column-major (Fortran-style);"A"
: (any)"F"
if a is Fortran contiguous,"C"
otherwise;"K"
: (keep) preserve input order;None
: preserve input order if possible,"C"
otherwise.
copy
:bool | None
, default=None
-
Whether to copy the underlying data.
True
: the object is copied;None
: the the object is copied only if needed;False
: raises aValueError
if a copy cannot be avoided.
owndata
:bool
, default=None
- If
True
, ensures that the returnedArray
owns its data. This may trigger an additional copy.
Returns
array
:Array
- Converted array.
def from_cell(other, **kwargs) ‑> Array
-
Convert a
Cell
to a numericArray
.Parameters
other
:Cell
- Cell to convert.
Other Parameters
dtype
:np.dtype | None
, default=None
- Target data type. Guessed if
None
. order
:{"C", "F", "A", "K"} | None
, default="K"
-
Memory layout.
"C"
: row-major (C-style);"F"
: column-major (Fortran-style);"A"
: (any)"F"
if a is Fortran contiguous,"C"
otherwise;"K"
: (keep) preserve input order.
owndata
:bool
, default=None
- If
True
, ensures that the returnedArray
owns its data. This may trigger an additional copy.
Returns
array
:Array
- Converted array.
def from_shape(shape=(), **kwargs) ‑> Array
-
Build an array of a given shape.
Parameters
shape
:list[int]
- Shape of new array.
Other Parameters
dtype
:np.dtype | None
, default='double'
- Target data type.
order
:{"C", "F"} | None
, default=None
-
Memory layout.
- "C" : row-major (C-style);
- "F" : column-major (Fortran-style).
Returns
array
:Array
- New array.
Instance variables
prop as_num : Array
-
Expand source code
@property def as_num(self) -> "Array": return self
class Cell (*args, **kwargs)
-
Expand source code
class Cell(_ListMixin, WrappedArray): """ Cell array, compatible with matlab cells. ```python # Instantiate from size Cell(N, M, ...) Cell([N, M, ...]) Cell.from_shape([N, M, ...]) # Instantiate from existing (cell-like) array (implicitely) Cell(cell_like) Cell.from_any(cell_like) # Other options Cell(..., order=None, *, copy=None, owndata=None, deepcat=False) ``` A cell is a `MutableSequence` and therefore (mostly) behaves like a list. It implements the following methods (which all operate along the 1st dimension, if the cell is a cell array): * `append` : Append object to the end of the cell * `clear` : Empty the cell * `count` : Number of occurrences of a value * `extend` : Extend list by appending elements from the iterable * `index` : First index of a value * `insert` : Insert object before index * `pop` : Remove and return item at index * `remove` : Remove first occurrence of value * `reverse` : Reverse the cell in-place * `sort` : Sort the list in ascending order in-place The magic operators `+` and `*` also operate as in lists: * `a + b` : Concatenate `a` and `b` * `a += b` : Append iterable `b` to `a` * `a * n` : Concatenate `n` repeats of `a` * `a *= n` : Append `n` repeats of `a` to `a` Finally, elements (along the first dimension) can be deleted using `del cell[index]`. !!! warning Lists or vectors of integers can be interpreted as shapes or as cell-like objects to copy. They are interpreted as shapes by the `Cell` constructor. To ensure that they are interpreted as arrays to copy, use `Cell.from_any`. """ # NOTE # _ListMixin must have precedence over _WrappedArray so that its # method overload those from np.ndarray. This is why the # inheritence order is (_ListMixin, _WrappedArray). _DelayedType = DelayedCell @classmethod def _DEFAULT(cls, shape: list = ()) -> np.ndarray: data = np.empty(shape, dtype=object) opt = dict( flags=["refs_ok", "zerosize_ok"], op_flags=["writeonly", "no_broadcast"] ) with np.nditer(data, **opt) as iter: for elem in iter: elem[()] = _empty_array() return data def _fill_default(self): arr = np.ndarray.view(self, np.ndarray) opt = dict( flags=["refs_ok", "zerosize_ok"], op_flags=["writeonly", "no_broadcast"] ) with np.nditer(arr, **opt) as iter: for elem in iter: elem[()] = _empty_array() return self def __new__(cls, *args, **kwargs) -> "Cell": mode, arg, kwargs = cls._parse_args(*args, **kwargs) kwargs["dtype"] = object if mode == "shape": if len(arg) == 0: # Scalar cells are forbidden arg = [0] return super().__new__(cls, shape=arg, **kwargs)._fill_default() else: return cls.from_any(arg, **kwargs) def _as_runtime(self) -> dict: if self.ndim == 1: return MatlabType._to_runtime(self.tolist()) else: size = np.array([[*np.shape(self)]]) data = np.ndarray.view(self, np.ndarray) data = np.reshape(data, [-1], order="F").tolist() data = MatlabType._to_runtime(data) return dict(type__="cell", size__=size, data__=data) @classmethod def _from_runtime(cls, objdict: dict, runtime=None) -> "Cell": if isinstance(objdict, (list, tuple, set)): shape = [len(objdict)] objdict = dict(type__="cell", size__=shape, data__=objdict) if objdict["type__"] != "cell": raise TypeError("objdict is not a cell") size = np.array(objdict["size__"], dtype=np.uint64).ravel() if len(size) == 2 and size[0] == 1: # NOTE: should not be needed for Cell, as this should # have been taken care of by MPython, but I am keeping it # here for symmetry with Array and Struct. size = size[1:] data = np.fromiter(objdict["data__"], dtype=object) data = data.reshape(size[::-1]).transpose() try: obj = data.view(cls) except Exception: raise RuntimeError( f"Failed to construct Cell data:\n" f" data={data}\n objdict={objdict}" ) # recurse opt = dict( flags=["refs_ok", "zerosize_ok"], op_flags=["readwrite", "no_broadcast"] ) with np.nditer(data, **opt) as iter: for elem in iter: elem[()] = MatlabType._from_runtime(elem.item(), runtime) return obj @classmethod def from_shape(cls, shape=tuple(), **kwargs) -> "Cell": """ Build a cell array of a given size. Parameters ---------- shape : list[int] Input shape. Other Parameters ---------------- order : {"C", "F"} | None, default="C" Memory layout. * `"C"` : row-major (C-style); * `"F"` : column-major (Fortran-style). Returns ------- cell : Cell New cell array. """ # Implement in __new__ so that cell owns its data return cls(list(shape), **kwargs) @classmethod def from_any(cls, other, **kwargs) -> "Cell": """ Convert a (nested) list-like object to a cell. Parameters ---------- other : CellLike object to convert. Other Parameters ---------------- deepcat : bool, default=False Convert cells of cells into cell arrays. order : {"C", "F", "A", "K"} | None, default=None Memory layout. * `"C"` : row-major (C-style); * `"F"` : column-major (Fortran-style); * `"A"` : (any) `"F"` if a is Fortran contiguous, `"C"` otherwise; * `"K"` : (keep) preserve input order; * `None`: preserve input order if possible, `"C"` otherwise. copy : bool | None, default=None Whether to copy the underlying data. * `True` : the object is copied; * `None` : the the object is copied only if needed; * `False`: raises a `ValueError` if a copy cannot be avoided. owndata : bool, default=False If `True`, ensures that the returned `Cell` owns its data. This may trigger an additional copy. Returns ------- cell : Cell Converted cell. """ # matlab object if isinstance(other, dict) and "type__" in other: return cls._from_runtime(other) kwargs["dtype"] = object deepcat = kwargs.pop("deepcat", False) # prepare for copy owndata = kwargs.pop("owndata", False) copy = None if owndata else kwargs.pop("copy", None) inp = other # recursive shallow conversion if not deepcat: # This is so list[list] are converted to Cell[Cell] and # not to a 2D Cell array. def asrecursive(other): if isinstance(other, tuple(_matlab_array_types())): dtype = _matlab_array_types()[type(other)] other = np.asarray(other, dtype=dtype) if isinstance(other, (np.ndarray, AnyDelayedArray)): return other elif isinstance(other, (str, bytes)): return other elif hasattr(other, "__iter__"): other = list(map(asrecursive, other)) tmp = np.ndarray(len(other), dtype=object) for i, x in enumerate(other): tmp[i] = x other = tmp obj = np.ndarray.view(other, cls) return obj else: return other other = asrecursive(other) if not isinstance(other, np.ndarray): other = np.asanyarray(other, **kwargs) # deep conversion else: other = np.asanyarray(other, **kwargs) other = cls._unroll_build(other) # as cell other = np.ndarray.view(other, cls) # copy (after view so that output owns data if copy=True) other = _copy_if_needed(other, inp, copy) # take ownership if owndata: tmp = other other = cls(tmp.shape, strides=tmp.strides) other[...] = tmp # recurse opt = dict( flags=["refs_ok", "zerosize_ok"], op_flags=["readwrite", "no_broadcast"] ) with np.nditer(other, **opt) as iter: for elem in iter: elem[()] = MatlabType.from_any(elem.item()) return other # aliases from_num = from_array = from_any @classmethod def _unroll_build(cls, arr): # The logic here is that we may sometimes end up with cells of # cells that must be converted to deep cell arrays. # To circumvent this, we find elements that are arrays, convert # them to lists, and recurse. rebuild = False arr = np.asarray(arr) opt = dict( flags=["refs_ok", "zerosize_ok"], op_flags=["readwrite", "no_broadcast"] ) with np.nditer(arr, **opt) as iter: for elem in iter: item = elem.item() if isinstance(item, np.ndarray): item = np.ndarray.view(item, object, np.ndarray) if item.ndim == 0: item = item.item() else: item = item.tolist() elem[()] = item rebuild = True if rebuild: # Recurse (we may have arrays of arrays of arrays...) return cls._unroll_build(arr.tolist()) return arr @property def as_cell(self) -> "Cell": return self def deepcat(self, owndata=None) -> "Cell": """ Convert a (nested) cell of cells into a deep cell array. """ cls = type(self) copy = self._unroll_build(np.copy(self)) copy = np.ndarray.view(copy, cls) # take ownership if owndata: tmp = copy copy = cls(tmp.shape, strides=tmp.strides) copy[...] = tmp return copy def __call__(self, *index): # We should only use this syntax when accessing elements into an # implictely created cell. In that context, we can just defer to # square bracket indexing. The point of using round brackets is # simply to instruct a DelayedArray that it should transform itself # into a Cell. # # NOTES: # # 1. We could implement round brackets only in DelayedArrays, # but I like the symmetry of having them in CellArrays too. # It enables things like # ```python # a.b(0).c = 'd' # Instructs that b is a Cell # a.b(1).c = 'e' # At this point b is already a Cell, # # but it's nicer to use the same syntax as above # ``` # # 2. This implementation means that using round brackets for # *accessing* data slices (`b(slice(2))`) does not have the # same behaviour as in matlab (`b{1:2}`). This is a very uncommon # use case, though. And single element indexing does work as # expected (python's `b(1)` == matlab's `b{2}`). # # 3. I substitute `slice` with `slice(None)` to make the syntax # for slicing whole axes more compact. # index = tuple(slice(None) if idx is slice else idx for idx in index) return self[index]
Cell array, compatible with matlab cells.
# Instantiate from size Cell(N, M, ...) Cell([N, M, ...]) Cell.from_shape([N, M, ...]) # Instantiate from existing (cell-like) array (implicitely) Cell(cell_like) Cell.from_any(cell_like) # Other options Cell(..., order=None, *, copy=None, owndata=None, deepcat=False)
A cell is a
MutableSequence
and therefore (mostly) behaves like a list. It implements the following methods (which all operate along the 1st dimension, if the cell is a cell array):append
: Append object to the end of the cellclear
: Empty the cellcount
: Number of occurrences of a valueextend
: Extend list by appending elements from the iterableindex
: First index of a valueinsert
: Insert object before indexpop
: Remove and return item at indexremove
: Remove first occurrence of valuereverse
: Reverse the cell in-placesort
: Sort the list in ascending order in-place
The magic operators
+
and*
also operate as in lists:a + b
: Concatenatea
andb
a += b
: Append iterableb
toa
a * n
: Concatenaten
repeats ofa
a *= n
: Appendn
repeats ofa
toa
Finally, elements (along the first dimension) can be deleted using
del mpython.cell[index]
.Warning
Lists or vectors of integers can be interpreted as shapes or as cell-like objects to copy. They are interpreted as shapes by the
Cell
constructor. To ensure that they are interpreted as arrays to copy, useCell.from_any()
.Ancestors
- mpython.core.mixin_types._ListMixin
- mpython.core.mixin_types._ListishMixin
- collections.abc.MutableSequence
- collections.abc.Sequence
- collections.abc.Reversible
- collections.abc.Collection
- collections.abc.Sized
- collections.abc.Iterable
- collections.abc.Container
- WrappedArray
- numpy.ndarray
- AnyWrappedArray
- AnyMatlabArray
- MatlabType
Static methods
def from_any(other, **kwargs) ‑> Cell
-
Convert a (nested) list-like object to a cell.
Parameters
other
:CellLike
- object to convert.
Other Parameters
deepcat
:bool
, default=False
- Convert cells of cells into cell arrays.
order
:{"C", "F", "A", "K"} | None
, default=None
-
Memory layout.
"C"
: row-major (C-style);"F"
: column-major (Fortran-style);"A"
: (any)"F"
if a is Fortran contiguous,"C"
otherwise;"K"
: (keep) preserve input order;None
: preserve input order if possible,"C"
otherwise.
copy
:bool | None
, default=None
-
Whether to copy the underlying data.
True
: the object is copied;None
: the the object is copied only if needed;False
: raises aValueError
if a copy cannot be avoided.
owndata
:bool
, default=False
- If
True
, ensures that the returnedCell
owns its data. This may trigger an additional copy.
Returns
cell
:Cell
- Converted cell.
def from_array(other, **kwargs) ‑> Cell
-
Convert a (nested) list-like object to a cell.
Parameters
other
:CellLike
- object to convert.
Other Parameters
deepcat
:bool
, default=False
- Convert cells of cells into cell arrays.
order
:{"C", "F", "A", "K"} | None
, default=None
-
Memory layout.
"C"
: row-major (C-style);"F"
: column-major (Fortran-style);"A"
: (any)"F"
if a is Fortran contiguous,"C"
otherwise;"K"
: (keep) preserve input order;None
: preserve input order if possible,"C"
otherwise.
copy
:bool | None
, default=None
-
Whether to copy the underlying data.
True
: the object is copied;None
: the the object is copied only if needed;False
: raises aValueError
if a copy cannot be avoided.
owndata
:bool
, default=False
- If
True
, ensures that the returnedCell
owns its data. This may trigger an additional copy.
Returns
cell
:Cell
- Converted cell.
def from_num(other, **kwargs) ‑> Cell
-
Convert a (nested) list-like object to a cell.
Parameters
other
:CellLike
- object to convert.
Other Parameters
deepcat
:bool
, default=False
- Convert cells of cells into cell arrays.
order
:{"C", "F", "A", "K"} | None
, default=None
-
Memory layout.
"C"
: row-major (C-style);"F"
: column-major (Fortran-style);"A"
: (any)"F"
if a is Fortran contiguous,"C"
otherwise;"K"
: (keep) preserve input order;None
: preserve input order if possible,"C"
otherwise.
copy
:bool | None
, default=None
-
Whether to copy the underlying data.
True
: the object is copied;None
: the the object is copied only if needed;False
: raises aValueError
if a copy cannot be avoided.
owndata
:bool
, default=False
- If
True
, ensures that the returnedCell
owns its data. This may trigger an additional copy.
Returns
cell
:Cell
- Converted cell.
def from_shape(shape=(), **kwargs) ‑> Cell
-
Build a cell array of a given size.
Parameters
shape
:list[int]
- Input shape.
Other Parameters
order
:{"C", "F"} | None
, default="C"
-
Memory layout.
"C"
: row-major (C-style);"F"
: column-major (Fortran-style).
Returns
cell
:Cell
- New cell array.
Instance variables
prop as_cell : Cell
-
Expand source code
@property def as_cell(self) -> "Cell": return self
Methods
def deepcat(self, owndata=None) ‑> Cell
-
Expand source code
def deepcat(self, owndata=None) -> "Cell": """ Convert a (nested) cell of cells into a deep cell array. """ cls = type(self) copy = self._unroll_build(np.copy(self)) copy = np.ndarray.view(copy, cls) # take ownership if owndata: tmp = copy copy = cls(tmp.shape, strides=tmp.strides) copy[...] = tmp return copy
Convert a (nested) cell of cells into a deep cell array.
class MatlabClass (*args, **kwargs)
-
Expand source code
class MatlabClass(MatlabType): """ Base class for wrapped MATLAB classes. The MATLAB package wrapped by mpython must define its own inheriting class that points to an appropriate runtime. Example ------- ```python class MyPackageRuntimeMixin: @classmethod def _runtime(cls): return MyPackageRuntime class MyPackageClass(MyPackageRuntimeMixin, MatlabClass): ... ``` """ _subclasses = dict() def __new__(cls, *args, _objdict=None, **kwargs): if _objdict is None: if cls.__name__ in MatlabClass._subclasses.keys(): obj = cls._runtime().call(cls.__name__, *args, **kwargs) else: obj = super().__new__(cls) else: obj = super().__new__(cls) obj._objdict = _objdict return obj def __init_subclass__(cls): super().__init_subclass__() if hasattr(cls, "subsref"): cls.__getitem__ = MatlabClass.__getitem cls.__call__ = MatlabClass.__call if hasattr(cls, "subsasgn"): cls.__setitem__ = MatlabClass.__setitem MatlabClass._subclasses[cls.__name__] = cls @classmethod def from_any(cls, other): if isinstance(other, dict) and "type__" in other: return cls._from_runtime(other) if not isinstance(other, cls): raise TypeError(f"Cannot convert {type(other)} to {cls}") return other @classmethod def _from_runtime(cls, objdict, runtime=None): if objdict["class__"] in MatlabClass._subclasses.keys(): obj = MatlabClass._subclasses[objdict["class__"]](_objdict=objdict) else: warnings.warn(f"Unknown Matlab class type: {objdict['class__']}") obj = MatlabClass(_objdict=objdict) return obj def _as_runtime(self): return self._objdict def __getattr(self, key): try: return self.subsref({"type": ".", "subs": key}) except Exception: raise AttributeError(key) def __getitem(self, ind): index = self._process_index(ind) try: return self.subsref({"type": "()", "subs": index}) except Exception: ... try: return self.subsref({"type": "{}", "subs": index}) except Exception: raise IndexError(index) def __setitem(self, ind, value): index = self._process_index(ind) try: return self.subsasgn({"type": "()", "subs": index}, value) except Exception: ... try: return self.subsasgn({"type": "{}", "subs": index}, value) except Exception: raise IndexError(index) def __call(self, *index): index = self._process_index(index) try: return self.subsref({"type": "{}", "subs": index}) except Exception: raise IndexError(index) def _process_index(self, ind, k=1, n=1): # FIXME: This should not need to call matlab try: return tuple( self._process_index(i, k + 1, len(ind)) for k, i in enumerate(ind) ) except TypeError: pass if not hasattr(self, "__endfn"): self.__endfn = self._runtime().call("str2func", "end") def end(): return self._runtime().call(self.__endfn, self._as_runtime(), k, n) if isinstance(ind, int): if ind >= 0: index = ind + 1 elif ind == -1: index = end() else: index = end() + ind - 1 elif isinstance(ind, slice): if ind.start is None and ind.stop is None and ind.step is None: index = ":" else: if ind.start is None: start = 1 elif ind.start < 0: start = end() + ind.start else: start = ind.start + 1 if ind.stop is None: stop = end() elif ind.stop < 0: stop = end() + ind.stop else: stop = ind.stop + 1 if ind.step is None: step = 1 else: step = ind.step min_ = min(start, stop) max_ = max(start, stop) if step > 0: index = np.arange(min_, max_, step) else: index = np.arange(max_, min_, step) else: index = ind return index
Base class for wrapped MATLAB classes.
The MATLAB package wrapped by mpython must define its own inheriting class that points to an appropriate runtime.
Example
class MyPackageRuntimeMixin: @classmethod def _runtime(cls): return MyPackageRuntime class MyPackageClass(MyPackageRuntimeMixin, MatlabClass): ...
Ancestors
Inherited members
class MatlabFunction (matlab_object, runtime)
-
Expand source code
class MatlabFunction(MatlabType): """ Wrapper for matlab function handles. End users should not have to instantiate such objects themselves. Example ------- ```python times2 = Runtime.call("eval", "@(x) 2.*x") assert(time2(1) == 2) ``` """ def __init__(self, matlab_object, runtime): super().__init__() matlab = _import_matlab() if not isinstance(matlab_object, matlab.object): raise TypeError("Expected a matlab.object") self._matlab_object = matlab_object self._runtime = runtime def _as_runtime(self): return self._matlab_object @classmethod def _from_runtime(cls, other, runtime): return cls(other, runtime) @classmethod def from_any(cls, other, runtime=None): if isinstance(other, MatlabFunction): return other return cls._from_runtime(other, runtime) def __call__(self, *args, **kwargs): return self._runtime.call(self._matlab_object, *args, **kwargs)
Wrapper for matlab function handles.
End users should not have to instantiate such objects themselves.
Example
times2 = Runtime.call("eval", "@(x) 2.*x") assert(time2(1) == 2)
Ancestors
Inherited members
class Runtime
-
Expand source code
class Runtime(ABC): """Namespace that holds the matlab runtime. Wrapped packages should implement their own inheriting class and define the `_import` method. Example ------- ```python class SPMRuntime(Runtime): @classmethod def _import_runtime(cls): import spm_runtime return spm_runtime ``` """ _instance = None verbose = True @classmethod @abstractmethod def _import_runtime(cls): """""" ... @classmethod def instance(cls): if cls._instance is None: if cls.verbose: print("Initializing Matlab Runtime...") cls._init_instance() return cls._instance @classmethod def call(cls, fn, *args, **kwargs): (args, kwargs) = cls._process_argin(*args, **kwargs) res = cls.instance().mpython_endpoint(fn, *args, **kwargs) return cls._process_argout(res) @classmethod def _process_argin(cls, *args, **kwargs): to_runtime = MatlabType._to_runtime args = tuple(map(to_runtime, args)) kwargs = dict(zip(kwargs.keys(), map(to_runtime, kwargs.values()))) return args, kwargs @classmethod def _process_argout(cls, res): return MatlabType._from_runtime(res, cls) @classmethod def _init_instance(cls): # NOTE(YB) # I moved the import within a function so that array wrappers # can be imported and used even when matlab is not properly setup. if cls._instance: return try: cls._instance = cls._import_runtime() # Make sure matlab is imported _import_matlab() except ImportError as e: print(cls._help) raise e _help = """ Failed to import package runtime. This can be due to a failure to find the MATLAB Runtime. Please verify that MATLAB Runtime is installed and can be discovered. See https://github.com/balbasty/matlab-runtime for instructions on how to install the MATLAB Runtime. If the issue persists, please open an issue with the entire error message at https://github.com/MPython-Package-Factory/mpython-core/issues. """
Namespace that holds the matlab runtime.
Wrapped packages should implement their own inheriting class and define the
_import
method.Example
class SPMRuntime(Runtime): @classmethod def _import_runtime(cls): import spm_runtime return spm_runtime
Ancestors
- abc.ABC
Class variables
var verbose
-
The type of the None singleton.
Static methods
def call(fn, *args, **kwargs)
def instance()
class SparseArray (*args, **kwargs)
-
Expand source code
class SparseArray(_SparseMixin, Array): """ Matlab sparse arrays (dense backend). ```python # Instantiate from size SparseArray(N, M, ...) SparseArray([N, M, ...]) SparseArray.from_shape([N, M, ...]) # Instantiate from existing sparse or dense array SparseArray(other_array) SparseArray.from_any(other_array) # Other options SparseArray(..., dtype=None, *, copy=None) ``` !!! warning Lists or vectors of integers can be interpreted as shapes or as dense arrays to copy. They are interpreted as shapes by the `SparseArray` constructor. To ensure that they are interpreted as dense arrays to copy, usse `SparseArray.from_any`. !!! note This is not really a sparse array, but a dense array that gets converted to a sparse array when passed to matlab. """ def to_dense(self) -> "Array": return np.ndarray.view(self, Array) @classmethod def from_coo(cls, values, indices, shape=None, **kw) -> "SparseArray": """ Build a sparse array from indices and values. Parameters ---------- values : (N,) ArrayLike Values to set at each index. indices : (D, N) ArrayLike Indices of nonzero elements. shape : list[int] | None Shape of the array. dtype : np.dtype | None Target data type. Same as `values` by default. Returns ------- array : SparseArray New array. """ dtype = kw.get("dtype", None) indices = np.asarray(indices) values = np.asarray(values, dtype=dtype) if shape is None: shape = (1 + indices.max(-1)).astype(np.uint64).tolist() if dtype is None: dtype = values.dtype obj = cls.from_shape(shape, dtype=dtype) obj[tuple(indices)] = values return obj
Matlab sparse arrays (dense backend).
# Instantiate from size SparseArray(N, M, ...) SparseArray([N, M, ...]) SparseArray.from_shape([N, M, ...]) # Instantiate from existing sparse or dense array SparseArray(other_array) SparseArray.from_any(other_array) # Other options SparseArray(..., dtype=None, *, copy=None)
Warning
Lists or vectors of integers can be interpreted as shapes or as dense arrays to copy. They are interpreted as shapes by the
SparseArray
constructor. To ensure that they are interpreted as dense arrays to copy, usseArray.from_any()
.Note
This is not really a sparse array, but a dense array that gets converted to a sparse array when passed to matlab.
Ancestors
- mpython.core.mixin_types._SparseMixin
- Array
- mpython.core.mixin_types._ListishMixin
- WrappedArray
- numpy.ndarray
- AnyWrappedArray
- AnyMatlabArray
- MatlabType
Static methods
def from_coo(values, indices, shape=None, **kw) ‑> SparseArray
-
Build a sparse array from indices and values.
Parameters
values
:(N,) ArrayLike
- Values to set at each index.
indices
:(D, N) ArrayLike
- Indices of nonzero elements.
shape
:list[int] | None
- Shape of the array.
dtype
:np.dtype | None
- Target data type. Same as
values
by default.
Returns
array
:SparseArray
- New array.
Methods
def to_dense(self) ‑> Array
-
Expand source code
def to_dense(self) -> "Array": return np.ndarray.view(self, Array)
Inherited members
class Struct (*args, **kwargs)
-
Expand source code
class Struct(_DictMixin, WrappedArray): """ Struct array, compatible with matlab structs. ```python # Instantiate from size Struct(N, M, ...) Struct([N, M, ...]) Struct.from_shape([N, M, ...]) # Instantiate from existing struct array # (or list/cell of dictionaries) Struct(struct_like) Struct.from_any(struct_like) # Instantiate from dictionary Struct(a=x, b=y) Struct({"a": x, "b": y}) Struct.from_any({"a": x, "b": y}) ``` The following field names correspond to existing attributes or methods of `Struct` objects and are therefore protected. They can still be used as field names, but only through the dictionary syntax (`s["shape"]`), not the dot syntax (`s.shape`): * `ndim -> int` : number of dimensions * `shape -> list[int]` : array shape * `size -> int` : number of elements * `reshape() -> Struct` : struct array with a different shape * `keys() -> list[str]` : field names * `values() -> list` : values (per key) * `items() -> [str, list]` : (key, value) pairs * `get() -> list` : value (per element) * `setdefault()` : sets default value for field name * `update()` : update fields from dictionary-like * `as_num -> raise` : interpret object as a numeric array * `as_cell -> raise` : interpret object as a cell array * `as_struct -> Struct` : interpret object as a struct array * `as_dict() -> dict` : convert to plain dictionary * `from_shape() -> Struct` : build a new empty struct * `from_any() -> Struct` : build a new struct by (shallow) copy * `from_cell() -> Struct` : build a new struct by (shallow) copy The following field names are protected because they have a special meaning in the python language. They can still be used as field names through the dictionary syntax: | | | | | | | |-|-|-|-|-|-| | `as` | `assert` | `break` | `class` | `continue`| `def` | | `del` | `elif` | `else` | `except` | `False` | `finally` | | `for` | `from` | `global` | `if` | `import` | `in` | | `is` | `lambda` | `None` | `nonlocal`| `not` | `or` | | `pass` | `raise` | `return` | `True` | `try` | `while` | | `with` | `yield` | """ # NOTE # _DictMixin must have precedence over _WrappedArray so that its # method overload those from np.ndarray. This is why the # inheritence order is (_DictMixin, _WrappedArray). # List of public attributes and methods from the ndarray class that # we keep in Struct. I've tried to find the minimal set of attributes # required to not break the numpy api. _NDARRAY_ATTRS = ("ndim", "shape", "size", "reshape") _DelayedType = DelayedStruct @classmethod def _DEFAULT(self, shape: list = ()) -> np.ndarray: # if len(shape) == 0: # out = np.array(None) # out[()] = dict() # return out data = np.empty(shape, dtype=dict) opt = dict( flags=["refs_ok", "zerosize_ok"], op_flags=["writeonly", "no_broadcast"] ) with np.nditer(data, **opt) as iter: for elem in iter: elem[()] = dict() return data def _fill_default(self): arr = np.ndarray.view(self, np.ndarray) flags = dict(flags=["refs_ok", "zerosize_ok"], op_flags=["readwrite"]) with np.nditer(arr, **flags) as iter: for elem in iter: elem[()] = dict() return self def __new__(cls, *args, **kwargs) -> "Struct": kwargs["__has_dtype"] = False kwargs["__has_order"] = False mode, arg, kwargs = cls._parse_args(*args, **kwargs) if mode == "shape": obj = super().__new__(cls, shape=arg, dtype=dict)._fill_default() else: obj = cls.from_any(arg) obj.update(kwargs) return obj def _as_runtime(self) -> dict: if self.ndim == 0: data = np.ndarray.view(self, np.ndarray).item() data = MatlabType._to_runtime(data) return data if np.ndarray.view(self, np.ndarray).size == 0: return dict(type__="emptystruct") size = np.array([[*np.shape(self)]]) data = np.ndarray.view(self, np.ndarray) data = np.reshape(data, [-1], order="F") data = MatlabType._to_runtime(data) return dict(type__="structarray", size__=size, data__=data) @classmethod def _from_runtime(cls, objdict: dict, runtime=None) -> "Struct": if objdict["type__"] != "structarray": raise TypeError("objdict is not a structarray") size = np.array(objdict["size__"], dtype=np.uint64).ravel() if len(size) == 2 and size[0] == 1: # NOTE: should not be needed for Cell, as this should # have been taken care of by MPython, but I am keeping it # here for symmetry with Array and Struct. size = size[1:] data = np.array(objdict["data__"], dtype=object) data = data.reshape(size) try: obj = data.view(cls) except Exception: raise RuntimeError( f"Failed to construct Struct data:\n" f" data={data}\n objdict={objdict}" ) # recurse opt = dict( flags=["refs_ok", "zerosize_ok"], op_flags=["readonly", "no_broadcast"] ) with np.nditer(data, **opt) as iter: for elem in iter: item = elem.item() for key, val in item.items(): item[key] = MatlabType._from_runtime(val, runtime) return obj @classmethod def from_shape(cls, shape=tuple(), **kwargs) -> "Struct": """ Build a struct array of a given size. Parameters ---------- shape : list[int] Input shape. Other Parameters ---------------- order : {"C", "F"} | None, default="C" Memory layout. * `"C"` : row-major (C-style); * `"F"` : column-major (Fortran-style). Returns ------- struct : Struct New struct array. """ return cls(list(shape), **kwargs) @classmethod def from_any(cls, other, **kwargs) -> "Struct": """ * Convert a dict-like object to struct; or * Convert an array of dict-like objects to a struct array. Parameters ---------- other : DictLike | ArrayLike[DictLike] object to convert. Other Parameters ---------------- order : {"C", "F", "A", "K"} | None, default=None Memory layout. * `"C"` : row-major (C-style); * `"F"` : column-major (Fortran-style); * `"A"` : (any) `"F"` if a is Fortran contiguous, `"C"` otherwise; * `"K"` : (keep) preserve input order; * `None`: preserve input order if possible, `"C"` otherwise. copy : bool | None, default=None Whether to copy the underlying data. * `True` : the object is copied; * `None` : the the object is copied only if needed; * `False`: raises a `ValueError` if a copy cannot be avoided. owndata : bool, default=None If `True`, ensures that the returned `Struct` owns its data. This may trigger an additional copy. Returns ------- struct : Struct Converted structure. """ if isinstance(other, dict) and "type__" in other: # matlab object return cls._from_runtime(other) kwargs["dtype"] = dict # prepare for copy owndata = kwargs.pop("owndata", False) copy = None if owndata else kwargs.pop("copy", None) inp = other # convert to array[dict] other = np.asanyarray(other, **kwargs) other = cls._unroll_build(other) # check all items are dictionaries arr = np.ndarray.view(other, np.ndarray) opt = dict(flags=["refs_ok", "zerosize_ok"], op_flags=["readonly"]) with np.nditer(arr, **opt) as iter: if not all(isinstance(elem.item(), dict) for elem in iter): raise TypeError("Not an array of dictionaries") # view as Struct other = np.ndarray.view(other, cls) # copy (after view so that output owns data if copy=True) other = _copy_if_needed(other, inp, copy) # take ownership if owndata: tmp = other other = cls(tmp.shape, dtype=tmp.dtype, strides=tmp.strides) other[...] = tmp # nested from_any opt = dict(flags=["refs_ok", "zerosize_ok"], op_flags=["readonly"]) with np.nditer(other, **opt) as iter: for elem in iter: item: dict = elem.item() for k, v in item.items(): item[k] = MatlabType.from_any(v) return other @classmethod def from_cell(cls, other, **kwargs) -> "Struct": """See `from_any`.""" Cell = _imports.Cell if not isinstance(other, Cell): raise TypeError(f"Expected a {Cell} but got a {type(other)}.") return cls.from_any(other, **kwargs) @classmethod def _unroll_build(cls, other): # The logic here is that we may sometimes end up with arrays of # arrays of dict rather than a single deep array[dict] # (for example when converting cells of cells of dict). # To circumvent this, we find elements that are arrays, convert # them to lists, and recurse. rebuild = False arr = np.ndarray.view(other, np.ndarray) flags = dict(flags=["refs_ok", "zerosize_ok"], op_flags=["readwrite"]) with np.nditer(arr, **flags) as iter: for elem in iter: item = elem.item() if isinstance(item, np.ndarray): item = np.ndarray.view(item, dict, np.ndarray) if item.ndim == 0: item = item.item() else: item = item.tolist() elem[()] = item rebuild = True if rebuild: # Recurse (we may have arrays of arrays of arrays...) return cls._unroll_build(other) return other @property def as_struct(self) -> "Struct": return self def __repr__(self): if self.ndim == 0: # Scalar struct -> display as a dict return repr(np.ndarray.view(self, np.ndarray).item()) else: return super().__repr__() def as_dict(self, keys=None) -> dict: """ Convert the object into a plain dict. * If a struct, return the underlying dict (no copy, is a view) * If a struct array, return a dict of Cell (copy, not a view) """ self._ensure_defaults_are_set(keys) # NOTE # The `keys` argument is only used in `__getattr__` to avoid # building the entire dictionary, when the content of a single # key is eventually used. # scalar struct -> return the underlying dictionary arr = np.ndarray.view(self, np.ndarray) if arr.ndim == 0: asdict = arr.item() if keys is not None: asdict = {key: asdict[key] for key in keys} return asdict # otherwise -> reverse array/dict order -> dict of cells of values if keys is None: keys = self._allkeys() elif isinstance(keys, str): keys = [keys] opt = dict(flags=["refs_ok", "zerosize_ok"], op_flags=["readwrite"]) asdict = {key: [] for key in keys} with np.nditer(arr, **opt) as iter: for elem in iter: item = elem.item() for key in keys: asdict[key].append(item[key]) Cell = _imports.Cell for key in keys: asdict[key] = Cell.from_any(asdict[key]) return asdict def _allkeys(self): # Return all keys present across all elements. # Keys are ordered by (1) element (2) within-element order mock = {} arr = np.ndarray.view(self, np.ndarray) opt = dict(flags=["refs_ok", "zerosize_ok"], op_flags=["readonly"]) with np.nditer(arr, **opt) as iter: for elem in iter: mock.update({key: None for key in elem.item().keys()}) return mock.keys() def _ensure_defaults_are_set(self, keys=None): """ If a new key is set in an array element, this function ensures that all other elements are assigned a default value in the new key. """ arr = np.ndarray.view(self, np.ndarray) if arr.ndim == 0: if keys: item: dict = arr.item() for key in keys: item.setdefault(key, _empty_array()) if keys is None: keys = self._allkeys() elif isinstance(keys, str): keys = [keys] opt = dict(flags=["refs_ok", "zerosize_ok"], op_flags=["readonly"]) with np.nditer(arr, **opt) as iter: for elem in iter: item: dict = elem.item() for key in keys: item.setdefault(key, _empty_array()) # -------------- # Bracket syntax # -------------- def __getitem__(self, index): if isinstance(index, str): try: return getattr(self, index) except AttributeError as e: raise KeyError(str(e)) else: obj = WrappedArray.__getitem__(self, index) if not isinstance(obj, (Struct, DelayedStruct)): # We've indexed a single element, but we do not want # to expose the underlying dictionary. Instead, # we return an empty-sized view of the element, which # is still of type `Struct`. if not isinstance(index, tuple): index = (index,) index += (None,) obj = np.ndarray.__getitem__(self, index) obj = np.reshape(obj, []) return obj def __setitem__(self, index, value): value = MatlabType.from_any(value) if isinstance(index, str): setattr(self, index, value) else: WrappedArray.__setitem__(self, index, value) self._ensure_defaults_are_set() def __delitem__(self, index): if isinstance(index, str): try: return delattr(self, index) except AttributeError as e: raise KeyError(str(e)) return WrappedArray.__delitem__(self, index) # ---------- # Dot syntax # ---------- def __getattribute__(self, key): # Hide public numpy attributes asnumpy = np.ndarray.view(self, np.ndarray) if ( hasattr(asnumpy, key) and key[:1] != "_" and key not in type(self)._NDARRAY_ATTRS ): raise AttributeError(f"hide numpy.ndarray.{key}") return super().__getattribute__(key) def __getattr__(self, key): if key[:1] == "_": raise AttributeError( f"{type(self).__name__} object has no attribute '{key}'" ) try: return _DictMixin.__getitem__(self, key) except KeyError as e: raise AttributeError(str(e)) def __setattr__(self, key, value): if key[:1] == "_": super().__setattr__(key, value) self._ensure_defaults_are_set() return try: if key in self._NDARRAY_ATTRS: # SyntaxWarning: overwriting numpy attributes warnings.warn( f"Field name '{key}' conflicts with an existing numpy attribute in {type(self).__name__}. " f"To avoid ambiguity, consider using dictionary-style access: {type(self).__name__}['{key}'] instead of dot notation.", SyntaxWarning, stacklevel=2, ) _DictMixin.__setitem__(self, key, value) self._ensure_defaults_are_set() return except KeyError as e: raise AttributeError(str(e)) def __delattr__(self, key): if key[:1] == "_": return super().__delattr__(key) try: return _DictMixin.__delitem__(self, key) except KeyError as e: raise AttributeError(str(e))
Struct array, compatible with matlab structs.
# Instantiate from size Struct(N, M, ...) Struct([N, M, ...]) Struct.from_shape([N, M, ...]) # Instantiate from existing struct array # (or list/cell of dictionaries) Struct(struct_like) Struct.from_any(struct_like) # Instantiate from dictionary Struct(a=x, b=y) Struct({"a": x, "b": y}) Struct.from_any({"a": x, "b": y})
The following field names correspond to existing attributes or methods of
Struct
objects and are therefore protected. They can still be used as field names, but only through the dictionary syntax (s["shape"]
), not the dot syntax (s.shape
):ndim -> int
: number of dimensionsshape -> list[int]
: array shapesize -> int
: number of elementsreshape() -> Struct
: struct array with a different shapekeys() -> list[str]
: field namesvalues() -> list
: values (per key)items() -> [str, list]
: (key, value) pairsget() -> list
: value (per element)setdefault()
: sets default value for field nameupdate()
: update fields from dictionary-likeas_num -> raise
: interpret object as a numeric arrayas_cell -> raise
: interpret object as a cell arrayas_struct -> Struct
: interpret object as a struct arrayas_dict() -> dict
: convert to plain dictionaryfrom_shape() -> Struct
: build a new empty structfrom_any() -> Struct
: build a new struct by (shallow) copyfrom_cell() -> Struct
: build a new struct by (shallow) copy
The following field names are protected because they have a special meaning in the python language. They can still be used as field names through the dictionary syntax:
as
assert
break
class
continue
def
del
elif
else
except
False
finally
for
from
global
if
import
in
is
lambda
None
nonlocal
not
or
pass
raise
return
True
try
while
with
yield
Ancestors
- mpython.core.mixin_types._DictMixin
- collections.abc.MutableMapping
- collections.abc.Mapping
- collections.abc.Collection
- collections.abc.Sized
- collections.abc.Iterable
- collections.abc.Container
- WrappedArray
- numpy.ndarray
- AnyWrappedArray
- AnyMatlabArray
- MatlabType
Static methods
def from_any(other, **kwargs) ‑> Struct
-
- Convert a dict-like object to struct; or
- Convert an array of dict-like objects to a struct array.
Parameters
other
:DictLike | ArrayLike[DictLike]
- object to convert.
Other Parameters
order
:{"C", "F", "A", "K"} | None
, default=None
-
Memory layout.
"C"
: row-major (C-style);"F"
: column-major (Fortran-style);"A"
: (any)"F"
if a is Fortran contiguous,"C"
otherwise;"K"
: (keep) preserve input order;None
: preserve input order if possible,"C"
otherwise.
copy
:bool | None
, default=None
-
Whether to copy the underlying data.
True
: the object is copied;None
: the the object is copied only if needed;False
: raises aValueError
if a copy cannot be avoided.
owndata
:bool
, default=None
- If
True
, ensures that the returnedStruct
owns its data. This may trigger an additional copy.
Returns
struct
:Struct
- Converted structure.
def from_cell(other, **kwargs) ‑> Struct
-
See
from_any
. def from_shape(shape=(), **kwargs) ‑> Struct
-
Build a struct array of a given size.
Parameters
shape
:list[int]
- Input shape.
Other Parameters
order
:{"C", "F"} | None
, default="C"
-
Memory layout.
"C"
: row-major (C-style);"F"
: column-major (Fortran-style).
Returns
struct
:Struct
- New struct array.
Instance variables
prop as_struct : Struct
-
Expand source code
@property def as_struct(self) -> "Struct": return self
Methods
def as_dict(self, keys=None) ‑> dict
-
Expand source code
def as_dict(self, keys=None) -> dict: """ Convert the object into a plain dict. * If a struct, return the underlying dict (no copy, is a view) * If a struct array, return a dict of Cell (copy, not a view) """ self._ensure_defaults_are_set(keys) # NOTE # The `keys` argument is only used in `__getattr__` to avoid # building the entire dictionary, when the content of a single # key is eventually used. # scalar struct -> return the underlying dictionary arr = np.ndarray.view(self, np.ndarray) if arr.ndim == 0: asdict = arr.item() if keys is not None: asdict = {key: asdict[key] for key in keys} return asdict # otherwise -> reverse array/dict order -> dict of cells of values if keys is None: keys = self._allkeys() elif isinstance(keys, str): keys = [keys] opt = dict(flags=["refs_ok", "zerosize_ok"], op_flags=["readwrite"]) asdict = {key: [] for key in keys} with np.nditer(arr, **opt) as iter: for elem in iter: item = elem.item() for key in keys: asdict[key].append(item[key]) Cell = _imports.Cell for key in keys: asdict[key] = Cell.from_any(asdict[key]) return asdict
Convert the object into a plain dict.
- If a struct, return the underlying dict (no copy, is a view)
- If a struct array, return a dict of Cell (copy, not a view)