I have a class with an attribute ax_mask. The attribute is list like. When I change the whole attribute or just an element of it, I need some other attribute (window) in the class to be updated (i.e. a function update() should be called that takes care of the update logic).

I have tried to implement the attribute as a descriptor like object. Here is my code:

import numpy as np


class AxMask:
    def __set_name__(self, owner, name):
        self.public_name = name
        self.private_name = '_' + name

    def __get__(self, obj, objtype=None):
        value = getattr(obj, self.private_name)
        return value

    def __set__(self, obj, value):
        setattr(obj, self.private_name, value)
        obj.update()

    def __setitem__(self, item, value):
        # change item in ax_mask to value
        # call Window.update()
        pass


class Window:
    ax_mask = AxMask()  # init descriptor like object

    def __init__(self, arr):
        self.arr = np.asarray(arr)
        self.window = self.arr  # init window
        shape = self.arr.shape
        self.ax_mask = [np.ones((d,), dtype=bool) for d in shape]  # init mask for each axis of arr with all True

    def update(self):
        # update window to only the rows/cols which are unmasked
        ix_ = np.ix_(*self.ax_mask)
        self.window = self.arr[ix_]

if __name__ == '__main__':
    m, n = (10, 5)
    test_arr = np.arange(m)[:, np.newaxis] + np.zeros((m, n), dtype=int)
    >>> test_arr
    array([[0, 0, 0, 0, 0],
           [1, 1, 1, 1, 1],
           [2, 2, 2, 2, 2],
           [3, 3, 3, 3, 3],
           [4, 4, 4, 4, 4],
           [5, 5, 5, 5, 5],
           [6, 6, 6, 6, 6],
           [7, 7, 7, 7, 7],
           [8, 8, 8, 8, 8],
           [9, 9, 9, 9, 9]])

    w = Window(test_arr)
    row_mask = np.ones((m,), dtype=bool)
    row_mask[5::] = False
    >>> row_mask
    array([True, True, True, True, True, False, False, False, False,
           False])

    w.ax_mask[0] = row_mask
    >>> w.window
    array([[0, 0, 0, 0, 0],
           [1, 1, 1, 1, 1],
           [2, 2, 2, 2, 2],
           [3, 3, 3, 3, 3],
           [4, 4, 4, 4, 4],
           [5, 5, 5, 5, 5],
           [6, 6, 6, 6, 6],
           [7, 7, 7, 7, 7],
           [8, 8, 8, 8, 8],
           [9, 9, 9, 9, 9]])

However I want w.window to look like this:

>>> w.window
array([[0, 0, 0, 0, 0],
    [1, 1, 1, 1, 1],
    [2, 2, 2, 2, 2],
    [3, 3, 3, 3, 3],
    [4, 4, 4, 4, 4]])

Any ideas are welcome!

1

There are 1 best solutions below

1
On

Thanks to the comments I came up with a solution. I hope this will help someone. Here is the code, I replaced class AxMask by ObservableList:

import numpy as np


class ObservableList(list):
    """Notifies the owner when an item is set"""
    def __init__(self, iterable, owner):
        super().__init__(iterable)
        self.owner = owner

    def __setitem__(self, key, value):
        super().__setitem__(key,value)
        self.notify()

    def notify(self):
        self.owner.update()


class Window:
    def __init__(self, arr):
        self.arr = np.asarray(arr)
        self.window = self.arr
        shape = self.arr.shape
        self._ax_mask = ObservableList([np.ones((d,), dtype=bool) for d in shape], self)

    @property
    def ax_mask(self):
        return self._ax_mask

    @ax_mask.setter
    def ax_mask(self, iterable):
        self._ax_mask = ObservableList(iterable, self)
        self.update()

    def update(self):
        ix_ = np.ix_(*self.ax_mask)  # advanced indexing -> no view, but copy;
        self.window = self.arr[ix_]

Any suggestions are welcome!