In a project, I created a class, and I needed an operation between this new class and a real matrix, so I overloaded the __rmul__
function like this
class foo(object):
aarg = 0
def __init__(self):
self.aarg = 1
def __rmul__(self,A):
print(A)
return 0
def __mul__(self,A):
print(A)
return 0
but when I called it, the result wasn't what I expected
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 seems that the function is called 6 times, once for each elements.
Instead, the __mul__
function works greatly
C = R * A
Output:
[[0 0]
[0 1]
[0 2]]
If A
isn't an array, but only a list of lists, both work fine
A = [[i*j for i in np.arange(2) ] for j in np.arange(3)]
R = foo()
C = A * R
C = R * A
Output
[[0, 0], [0, 1], [0, 2]]
[[0, 0], [0, 1], [0, 2]]
I'd really want for my __rmul__
function to work also on arrays (my original multiplication function isn't commutative). How can I solve it?
The Python __rmul__() method implements the reverse multiplication operation that is multiplication with reflected, swapped operands.
array() in Python. The homogeneous multidimensional array is the main object of NumPy. It is basically a table of elements which are all of the same type and indexed by a tuple of positive integers. The dimensions are called axis in NumPy.
NumPy arrays have a fixed size at creation, unlike Python lists (which can grow dynamically). Changing the size of an ndarray will create a new array and delete the original. The elements in a NumPy array are all required to be of the same data type, and thus will be the same size in memory.
The [:, :] stands for everything from the beginning to the end just like for lists. The difference is that the first : stands for first and the second : for the second dimension. a = numpy. zeros((3, 3)) In [132]: a Out[132]: array([[ 0., 0., 0.], [ 0., 0., 0.], [ 0., 0., 0.]])
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With