Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Changing subclassed `ndarray` view in-place

Tags:

python

numpy

Suppose I have a simple Test class which inherits from numpy.ndarray (documentation on ndarray subclassing). This class creates an object which is a partial view of another ndarray. Now I would like to create a method that modifies this view in-place. Something like:

import numpy


class Test(numpy.ndarray):

    def __new__(cls, info='Test class'):
        base = numpy.ndarray.__new__(cls, (6, 2), dtype=float)
        view = base[0:2, :]
        view.info = info
        return view

    def __array_finalize__(self, obj):
        if obj is None:
            return
        self.info = getattr(obj, 'info', None)

    def change_view(self, start, stop):
        """This is the method I'd like to implement"""
        # Incorrect, but represents what I am looking for
        self = self.base[start:stop]


if __name__ == '__main__':
    t = Test()
    print(t)
    print(t.info)
    t.change_view(1, 5)
    print(t)
    print(t.info)

I would expect the method .change_view(x) to modify the view of the .base attribute to show rows [1:5] instead of [0:2], which is the default in __new__().

Is this possible? If so, how? Otherwise, why not? Note that .base never changes (it always occupies the same memory space), I just want to change the view of it.

Notes

  • See the comments in change_view(). I know self = self.base[start:stop] is not correct, and I know and understand why, but I think it is a short way to represent what I am looking for.
  • As noted in the answers bellow, one could think of self[:] = ... or self.data = .... However, this will not work when changing the view size (which is exactly the case).

References

This question was already asked here (or at least it is a similar case), but answers referred to solutions which returned the modified array (i.e. no in-place modification) or which used a wrapper object (i.e. having an attribute with the array that is to be modified).

I am aware of those possibilities, and I know they can be simpler or even more convenient. However, I would like to understand why (if so) it is not possible to do what I am looking for.

like image 207
Peque Avatar asked Jan 20 '16 09:01

Peque


2 Answers

1) You can't change what data an ndarray instance or subclass instance points to in-place. Sorry! It turns out that it is possible to change which data an ndarray instance points to in-place, but it's a terrible idea and will hopefully be removed soon :-). See https://github.com/numpy/numpy/issues/7093

2) Your attempt to assign to self like that will only change the binding of the self variable inside the method's local binding, and I'm afraid this suggests a fairly fundamental misunderstanding of how Python's execution and object model work. I only bring this up because successfully subclassing ndarray is hard. Really hard. Verging on impossible. (The developers of the popular pandas library gave up, for example.) Despite the misleading impression given by that documentation, numpy is not really designed to support subclassing, and it does not work very well. I'd generally recommend that nobody at all attempt to subclass ndarray; if you're not already an expert Python programmer than this goes tenfold. Probably we would have deleted that stupid page and disabled subclassing altogether except that it would break backwards compatibility :-(. I'd suggest finding a different strategy to approach your problem.

like image 51
Nathaniel J. Smith Avatar answered Nov 16 '22 02:11

Nathaniel J. Smith


When you write code like

var = value

it assigns new value to var and drops old value, however this is true only in current scope and all other aliases to previous value of var stay unchanged. Python's self is nothing special, it's just a variable, hence when you write

self = self.base[start:stop]

you just assign value of self.base[start:stop] to the local variable self but the value previsouly contained in self was not changed as well as all other references that value (which is an instance of your Test class that you want to mutate). You can test it with the following implementation of change_view:

def change_view(self, start, stop):
    """This is the method I'd like to implement"""
    # Incorrect, but represents what I am looking for
    print('before assignment')
    print(self)
    self = self.base[start:stop]
    print('after assignment')
    print(self)
    print('end change_view')

I think running your code with this implementation should clear things up a bit. Possible implementation might be similar to this:

def change_view(self, start, stop):
    """This is the method I'd like to implement"""
    # Incorrect, but represents what I am looking for
    self[:] = self.base[start:stop]

The important difference is that it mutates the value contained in the variable self instead of assigning new value to the variable. I think something like this should work with python lists, but numpy.ndarray is more complex. Moreover since it's implemented in C it might be impossible at all. For example you'll need to resize the view somehow, but you can't use numpy.ndarray.resize because views don't own it's data; simply assigning size and shape won't work too.

like image 28
zaquest Avatar answered Nov 16 '22 01:11

zaquest