How to make the elements of a NumPy array property settable?
Question:
I have a property of a Python object that returns an array.
Now, I can set the setter of that property such that the whole array is settable.
However, I’m missing how to make the elements by themselves settable through the property.
I would expect from a user perspective (given an empty SomeClass
class):
>>> x = SomeClass()
>>> x.array = [1, 2, 3]
>>> x.array[1] = 4
>>> print (x.array)
[1, 4, 3]
Now, suppose that SomeClass.array
is a property defined as
class SomeClass(object):
def __init__(self, a):
self._a = a
@property
def array(self):
return self._a
@array.setter
def array(self, a):
self._a = a
Everything still works as above. Also if I force simple NumPy arrays on the setter.
However, if I replace the return self._a
with a NumPy function (that goes in a vectorised way through the elements) and I replace self._a = a
with the inverse function, of course the entry does not get set anymore.
Example:
import numpy as np
class SomeClass(object):
def __init__(self, a):
self._a = np.array(a)
@property
def array(self):
return np.sqrt(self._a)
@array.setter
def array(self, a):
self._a = np.power(a, 2)
Now, the user sees the following output:
>>> x = SomeClass([1, 4, 9])
>>> print (x.array)
array([1., 2., 3.])
>>> x.array[1] = 13
>>> print (x.array) # would expect an array([1., 13., 3.]) now!
array([1., 2., 3.])
I think I understand where the problem comes from (the array that NumPy creates during the operation gets its element changed but it doesn’t have an effect on the stored array).
What would be a proper implementation of SomeClass
to make single elements of the array write-accessible individually and thus settable as well?
Thanks a lot for your hints and help,
TheXMA
The points @Jaime made below his answer helped me a lot! Thanks!
Answers:
Since arrays are mutable objects, the individual items are settable even without a setter function:
>>> class A(object):
... def __init__(self, a):
... self._a = np.asarray(a)
... @property
... def arr(self):
... return self._a
...
>>> a = A([1,2,3])
>>> a.arr
array([1, 2, 3])
>>> a.arr = [4,5,6] # There is no setter...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>> a.arr[1] = 7 # ...but the array is mutable
>>> a.arr
array([1, 7, 3])
This is one of the uses of tuples vs. lists, since the latter are mutable, but the former aren’t. Anyway, to answer your question: making individual items settable is easy, as long as your getter returns the object itself.
The fancier performance in your second example doesn’t seem easy to get in any simple way. I think you could make it happen by making your SomeClass.array
attribute be a custom class, that either subclasses ndarray
or wraps an instance of it. Either way would be a lot of nontrivial work.
To make array elements settable when the underlying object isn’t returned you must use a subclass of both the property
and ndarray
classes. The __get__
method of the property
class returns before the __getitem__
or __setitem__
methods of the ndarray
are called, so it can be used to patch in the desired getters and setters for the array. With the appropriate subclasses, all that needs to change for the implementation in your own class is the addition of a keyword and logic for handling the indexing. Your example would look like the following using the DescriptorArray
and ArrayProperty
classes defined below:
import numpy as np
class SomeClass():
def __init__(self, a):
self._a = np.array(a)
@ArrayProperty
def array(self, key=...):
return np.sqrt(self._a[key])
@array.setter
def array(self, a, key=...):
self._a[key] = np.power(a, 2)
The __getitem__
and __setitem__
methods of the array use the getter and setter defined in your class, and the results are as expected:
>>> x = SomeClass([1., 4., 9.])
>>> print(x.array)
[1. 2. 3.]
>>> x.array[1] = 13
>>> print(x.array)
[ 1. 13. 3.]
>>> print(x._a)
[ 1. 169. 9.]
The ndarray
and property
subclasses used in this example were defined as follows:
class DescriptorArray(np.ndarray):
item_getter = item_setter = None
def __getitem__(self, key):
if self.item_getter is None:
return super().__getitem__(key)
else:
return self.item_getter(key)
def __setitem__(self, key, value):
if self.item_setter is None:
super().__setitem__(key, value)
else:
self.item_setter(key, value) # set underlying object
self.base[key] = self[key] # update copy of processed result
class ArrayProperty(property):
def __get__(self, obj, objtype):
# Get Array
array = super().__get__(obj, objtype)
# Define Item Getter and Setter
def item_getter(key):
return self.fget(obj, key)
def item_setter(key, value):
if self.fset is None:
self.__set__(obj, value) # to raise AttributeError
self.fset(obj, value, key)
# Return DescriptorArray
dsc_array = array.view(DescriptorArray) # view casting
dsc_array.item_getter = item_getter
dsc_array.item_setter = item_setter
return dsc_array
The code for DescriptorArray
has some quirks specific to NumPy ndarrays since their subclasses typically propagate through other operations (so it needs to still work when the getter and setter are undefined), but the general procedure of overriding the __getitem__
and __setitem__
methods of the object returned by the __get__
method should work for other container types.
I have a property of a Python object that returns an array.
Now, I can set the setter of that property such that the whole array is settable.
However, I’m missing how to make the elements by themselves settable through the property.
I would expect from a user perspective (given an empty SomeClass
class):
>>> x = SomeClass()
>>> x.array = [1, 2, 3]
>>> x.array[1] = 4
>>> print (x.array)
[1, 4, 3]
Now, suppose that SomeClass.array
is a property defined as
class SomeClass(object):
def __init__(self, a):
self._a = a
@property
def array(self):
return self._a
@array.setter
def array(self, a):
self._a = a
Everything still works as above. Also if I force simple NumPy arrays on the setter.
However, if I replace the return self._a
with a NumPy function (that goes in a vectorised way through the elements) and I replace self._a = a
with the inverse function, of course the entry does not get set anymore.
Example:
import numpy as np
class SomeClass(object):
def __init__(self, a):
self._a = np.array(a)
@property
def array(self):
return np.sqrt(self._a)
@array.setter
def array(self, a):
self._a = np.power(a, 2)
Now, the user sees the following output:
>>> x = SomeClass([1, 4, 9])
>>> print (x.array)
array([1., 2., 3.])
>>> x.array[1] = 13
>>> print (x.array) # would expect an array([1., 13., 3.]) now!
array([1., 2., 3.])
I think I understand where the problem comes from (the array that NumPy creates during the operation gets its element changed but it doesn’t have an effect on the stored array).
What would be a proper implementation of SomeClass
to make single elements of the array write-accessible individually and thus settable as well?
Thanks a lot for your hints and help,
TheXMA
The points @Jaime made below his answer helped me a lot! Thanks!
Since arrays are mutable objects, the individual items are settable even without a setter function:
>>> class A(object):
... def __init__(self, a):
... self._a = np.asarray(a)
... @property
... def arr(self):
... return self._a
...
>>> a = A([1,2,3])
>>> a.arr
array([1, 2, 3])
>>> a.arr = [4,5,6] # There is no setter...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>> a.arr[1] = 7 # ...but the array is mutable
>>> a.arr
array([1, 7, 3])
This is one of the uses of tuples vs. lists, since the latter are mutable, but the former aren’t. Anyway, to answer your question: making individual items settable is easy, as long as your getter returns the object itself.
The fancier performance in your second example doesn’t seem easy to get in any simple way. I think you could make it happen by making your SomeClass.array
attribute be a custom class, that either subclasses ndarray
or wraps an instance of it. Either way would be a lot of nontrivial work.
To make array elements settable when the underlying object isn’t returned you must use a subclass of both the property
and ndarray
classes. The __get__
method of the property
class returns before the __getitem__
or __setitem__
methods of the ndarray
are called, so it can be used to patch in the desired getters and setters for the array. With the appropriate subclasses, all that needs to change for the implementation in your own class is the addition of a keyword and logic for handling the indexing. Your example would look like the following using the DescriptorArray
and ArrayProperty
classes defined below:
import numpy as np
class SomeClass():
def __init__(self, a):
self._a = np.array(a)
@ArrayProperty
def array(self, key=...):
return np.sqrt(self._a[key])
@array.setter
def array(self, a, key=...):
self._a[key] = np.power(a, 2)
The __getitem__
and __setitem__
methods of the array use the getter and setter defined in your class, and the results are as expected:
>>> x = SomeClass([1., 4., 9.])
>>> print(x.array)
[1. 2. 3.]
>>> x.array[1] = 13
>>> print(x.array)
[ 1. 13. 3.]
>>> print(x._a)
[ 1. 169. 9.]
The ndarray
and property
subclasses used in this example were defined as follows:
class DescriptorArray(np.ndarray):
item_getter = item_setter = None
def __getitem__(self, key):
if self.item_getter is None:
return super().__getitem__(key)
else:
return self.item_getter(key)
def __setitem__(self, key, value):
if self.item_setter is None:
super().__setitem__(key, value)
else:
self.item_setter(key, value) # set underlying object
self.base[key] = self[key] # update copy of processed result
class ArrayProperty(property):
def __get__(self, obj, objtype):
# Get Array
array = super().__get__(obj, objtype)
# Define Item Getter and Setter
def item_getter(key):
return self.fget(obj, key)
def item_setter(key, value):
if self.fset is None:
self.__set__(obj, value) # to raise AttributeError
self.fset(obj, value, key)
# Return DescriptorArray
dsc_array = array.view(DescriptorArray) # view casting
dsc_array.item_getter = item_getter
dsc_array.item_setter = item_setter
return dsc_array
The code for DescriptorArray
has some quirks specific to NumPy ndarrays since their subclasses typically propagate through other operations (so it needs to still work when the getter and setter are undefined), but the general procedure of overriding the __getitem__
and __setitem__
methods of the object returned by the __get__
method should work for other container types.