Hauke
Hauke

Reputation: 2574

Numpy arrays and python properties

I would like to perform checks on the contents of a numpy array every time it is set. Can python properties be used for this? My approach:

import numpy as np

class Obj(): 
    def __init__(self):                                                                                 
        self._np_arr = None                                                                             

    @property                                                                                           
    def np_arr(self):                                                                                   
        if self._np_arr is None:                                                                        
            self._np_arr = np.ones(10)                                                                  
        return self._np_arr                                                                             

    @np_arr.setter
    def np_arr(self, value):
        if np.sum(value)>10:
            raise ValueError('Error message')                                                           
        self._np_arr = value

if __name__ == '__main__':
    o = Obj()                                                                                           
    print o.np_arr
    o.np_arr = np.zeros(10) # ok                                                                        
    o.np_arr = np.ones(10)*2 # not ok                                                                   
    print o.np_arr       

The getter is entered when the object is still None. Once np_arr is a numpy array the getters and setters no longer work.

What am I doing wrong?

Upvotes: 3

Views: 987

Answers (3)

Thomas Vincent
Thomas Vincent

Reputation: 198

There is a some naming conflict in using the '_' prefix to mean that it's private while also defining get/set properties to directly access it as o._np_arr.

If you want it to be really private, maybe you'd rather define explicit get/setters (o.set_arr, o.get_arr) and invite the user not to modify it inplace.

Upvotes: 0

seberg
seberg

Reputation: 8975

First of all. This is difficult (up to, I believe impossible) to do right. The error here is quite simple, it must be a new style class. So just replace class Obj() with class Obj(object).

This may not solve your problem however, since the user can still assign it in place, ie:

o.np_arr[:] = 2

You could circumvent that somewhat by using _np_arr.setflags(write=False), but then the user cannot use such operations at all.

Edit: you could also subclass ndarray, and define your own __array_finalize__ to circumvent that probably. However, even then the user could do np.asarray(o.np_arr) and modify that in place...

I guess you could provide your own methods then "simulate" an array by wroking internally on a writeable one. But I doubt there is a 100% foolproof or at least elegent method.

Upvotes: 3

lvc
lvc

Reputation: 35089

Properties only work properly on new-style classes - changing class Obj(): to class Obj(object): gives the expected output:

$ python2 test.py
[ 1.  1.  1.  1.  1.  1.  1.  1.  1.  1.]
Traceback (most recent call last):
  File "test.py", line 22, in <module>
    o.np_arr = np.ones(10) * 2
  File "test.py", line 16, in np_arr
    raise ValueError('Error message')
ValueError: Error message

When Obj is an old-style class (the default in Python 2), assigning to o.np_arr doesn't call the setter - it behaves like a regular attribute assignment, and clobbers the property.

Upvotes: 3

Related Questions