How can I add the behavior of `quantity.Quantity` without subclassing with` __array__`?

quantities.Quantity is a subclass of numpy.ndarray that handles arithmetic and conversion of physical quantities. How can I use its arithmetic without subclassing? The following approach uses the __array__ method, but it just works at 80%, as you can see at the end:

 class Numeric(object): def __init__(self, signal): self.signal = signal self._dimensionality = self.signal._dimensionality self.dimensionality = self.signal.dimensionality def __array__(self): return self.signal def __mul__(self, obj): return self.signal.__mul__(obj) def __rmul__(self, obj): return self.signal.__rmul__(obj) 

With this I can do:

 import quantities as pq import numpy as np num = Numeric(pq.Quantity([1,2,3], 'mV')) q = pq.Quantity([2,3,4], 'mV') n = np.array([3,4,5]) 

All of the following operations return the correct block - with the exception of the last, there is no one there :

 print num * num # [1 4 9] mV**2 print num * q # [ 2 6 12] mV**2 print num * n # [ 3 8 15] mV print q * num # [ 2 6 12] mV**2 print n * num # [ 3 8 15] <------- no unit! 

Any idea what to fix to keep the correct unit?

edit . The type / value of the return value for an arithmetic operation should be equivalent to:

  • num.signal * num.signal
  • num.signal * q
  • num.signal * n
  • q * num.signal
  • n * num.signal # this doesn't work
+4
source share
3 answers

When Python sees x * y here, what happens:

  • if y is a subclass of xy.__rmul__(x) , is called

otherwise:

  • x.__mul__(y) is called

IF x.__mul__(y) returns NotImplemented (which is different from raise NotImplementedError

  • y.__rmul__(x) is called

So there are two ways that __rmul__ can be called - a subclass of ndarray , or ndarray will not be able to multiply with Numeric .

You cannot subclass, and apparently ndarray happy to work with Numeric , therefore .,.

Fortunately, numpy people prepared for such situations - the answer lies in the __array_wrap__ method:

 def __array_wrap__(self, out_arr, context=None): return type(self.signal)(out_arr, self.dimensionality) 

We use the original signal class along with the original dimension to create a new signal for the new Numeric object.

The whole bit looks like this:

 import quantities as pq import numpy as np class Numeric(object): def __init__(self, signal): self.signal = signal self.dimensionality = self.signal.dimensionality self._dimensionality = self.signal._dimensionality def __array__(self): return self.signal def __array_wrap__(self, out_arr, context=None): return type(self.signal)(out_arr, self.dimensionality) def __mul__(self, obj): return self.signal.__mul__(obj) def __rmul__(self, obj): return self.signal.__rmul__(obj) num = Numeric(pq.Quantity([1,2,3], 'mV')) q = pq.Quantity([2,3,4], 'mV') n = np.array([3,4,5]) t = num * num print type(t), t t = num * q print type(t), t t = num * n print type(t), t t = q * num print type(t), t t = n * num print type(t), t 

And at startup:

 <class 'quantities.quantity.Quantity'> [1 4 9] mV**2 <class 'quantities.quantity.Quantity'> [ 2 6 12] mV**2 <class 'quantities.quantity.Quantity'> [ 3 8 15] mV <class 'quantities.quantity.Quantity'> [ 2 6 12] mV**2 <class 'quantities.quantity.Quantity'> [ 3 8 15] mV 
+4
source

You need to define __array_wrap__ . See the documentation here .

As a quick example using your example (but not requiring quantities ):

 class Numeric(object): def __init__(self, signal): self.signal = signal def __array__(self): return self.signal def __mul__(self, obj): return type(self)(self.signal.__mul__(obj)) def __rmul__(self, obj): return type(self)(self.signal.__rmul__(obj)) import numpy as np num = Numeric(np.arange(10)) n = np.arange(10) print type(num * n) print type(n * num) 

This gives:

 <class '__main__.Numeric'> <type 'numpy.ndarray'> 

If we __array_wrap__ :

 class Numeric(object): def __init__(self, signal): self.signal = signal def __array__(self): return self.signal def __mul__(self, obj): return type(self)(self.signal.__mul__(obj)) def __rmul__(self, obj): return type(self)(self.signal.__rmul__(obj)) def __array_wrap__(self, out_arr, context=None): return type(self)(out_arr) import numpy as np num = Numeric(np.arange(10)) n = np.arange(10) print type(num * n) print type(n * num) 

This gives:

 <class '__main__.Numeric'> <class '__main__.Numeric'> 

However, I'm still confused about why you can't just subclass ndarray in the first place ... I suspect that in the long run it will be much cleaner. If you cannot, you cannot, however.

To fully simulate ndarray without subclassing ndarray , you will need to be very familiar with the details of their subclassification .

+3
source

According to the python reference, __rmul__ right operand "is called only if the left operand does not support the corresponding operation and the operands are of different types.

So the problem is that when running n * num.signal numpy.array supports multiplication and takes over. The only way I see around is to do it if there is a way to make Numeric incompatible with numpy.array.__mul__

+1
source

Source: https://habr.com/ru/post/1399414/


All Articles