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, use Array.from_any().

Ancestors

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 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.
def from_cell(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.
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_numArray
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 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 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, use Cell.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 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.
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 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.
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 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.
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_cellCell
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, usse Array.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

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 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

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 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.
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_structStruct
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)