Array and __rmul__ operator in Python Numpy Array and __rmul__ operator in Python Numpy numpy numpy

Array and __rmul__ operator in Python Numpy


The behaviour is expected.

First of all you have to understand how an operation like x*y is actually executed. The python interpreter will first try to compute x.__mul__(y).If this call returns NotImplemented it will then try to compute y.__rmul__(x).Except when y is a proper subclass of the type of x, in this case the interpreter will first consider y.__rmul__(x) and then x.__mul__(y).

Now what happens is that numpy treats arguments differently depending on whether or not he thinks the argument are scalar or arrays.

When dealing with arrays * does element-by-element multiplication, while scalar multiplication multiplies all the entry of an array by the given scalar.

In your case foo() is considered as a scalar by numpy, and thus numpy multiplies all elements of the array by foo. Moreover, since numpy doesn't know about the type foo it returns an array with dtype=object, so the object returned is:

array([[0, 0],       [0, 0],       [0, 0]], dtype=object)

Note: numpy's array does not return NotImplemented when you try to compute the product, so the interpreter calls numpy's array __mul__ method, which performs scalar multiplication as we said. At this point numpy will try to multiply each entry of the array by your "scalar" foo(), and here's is where your __rmul__ method gets called, because the numbers in the array return NotImplemented when their __mul__ is called with a foo argument.

Obviously if you change the order of the arguments to the initial multiplication your __mul__ method gets called immediately and you don't have any trouble.

So, to answer your question, one way to handle this is to have foo inherit from ndarray, so that the second rule applies:

class foo(np.ndarray):    def __new__(cls):       # you must implement __new__    # code as before

Warning however that subclassing ndarray isn't straightforward.Moreover you might have other side effects, since now your class is an ndarray.


You can define __numpy_ufunc__ function in your class. It works even without subclassing the np.ndarray. You can find the documentation here.

Here is an example based on your case:

class foo(object):    aarg = 0    def __init__(self):        self.aarg = 1    def __numpy_ufunc__(self, *args):        pass    def __rmul__(self,A):        print(A)        return 0    def __mul__(self,A):        print(A)        return 0

And if we try it,

A = [[i*j for i in np.arange(2)  ] for j in np.arange(3)]A = np.array(A)R = foo()C =  A * R

Output:

[[0 0] [0 1] [0 2]]

It works!


I could not explain the underlying problem as precise as Bakuriu, but there might be another solution.

You can force numpy to use your evaluation method by defining __array_priority__. As explained here in the numpy docs.

In your case you had to change your class definition to:

MAGIC_NUMBER = 15.0# for the necessary lowest values of MAGIC_NUMBER look into the numpy docsclass foo(object):    __array_priority__ = MAGIC_NUMBER    aarg = 0    def __init__(self):        self.aarg = 1    def __rmul__(self,A):        print(A)        return 0    def __mul__(self,A):        print(A)        return 0