Python: Multiplication Override

Python: multiplication override

Just add the following to the class definition and you should be good to go:

__rmul__ = __mul__

Operator overloading in Python: handling different types and order of parameters

You also need to implement __rmul__. When the initial call to int.__mul__(7, v) fails, Python will next try type(v).__rmul__(v, 7).

def __rmul__(self, lhs):
return self * lhs # Effectively, turn 7 * v into v * 7

As Rawing points out, you could simply write __rmul__ = __mul__ for this definition. __rmul__ exists to allow for non-commutative multiplication where simply deferring to __mul__ with the operands reversed isn't sufficient.

For instance, if you were writing a Matrix class and wanted to support multiplication by a nested list, e.g.,

m = Matrix(...)  # Some 2 x 2 matrix
n = [[1, 2], [3,4]]
p = n * m

Here, the list class wouldn't know how to multiple a list by a Matrix instance, so when list.__mul__(n, m) fails, Python would next try Matrix.__rmul__(m, n). However, n * m and m * n are two different results in general, so Matrix.__rmul__(m, n) != Matrix.__mul__(m, n); __rmul__ has to do a little extra work to generate the right answer.

Python - multiplication between a variable that belong to the same class

To get this to work, do this:

otherclass = other.x*other.y

instead of

otherclass = other

This will mean otherclass is an int and the multiplication will work.

Overriding other __rmul__ with your class's __mul__

The easiest way to make NumPy respect your __rmul__ method is to set an __array_priority__:

class AbstractMatrix(object):
def __init__(self):
self.data = np.array([[1, 2],[3, 4]])

def __mul__(self, other):
return np.dot(self.data, other)

def __rmul__(self, other):
return np.dot(other, self.data)

__array_priority__ = 10000

A = AbstractMatrix()
B = np.array([[4, 5],[6, 7]])

This works like expected.

>>> B*A
array([[19, 28],
[27, 40]])

The problem is that NumPy doesn't respect Pythons "Numeric" Data model. If a numpy array is the first argument and numpy.ndarray.__mul__ isn't possible then it tries something like:

result = np.empty(B.shape, dtype=object)
for idx, item in np.ndenumerate(B):
result[idx] = A.__rmul__(item)

However if the second argument has an __array_priority__ and it's higher than the one of the first argument only then it really uses:

A.__rmul__(B)

However since Python 3.5 (PEP-465) there is the @ (__matmul__) operator that can utilize matrix multiplication:

>>> A = np.array([[1, 2],[3, 4]])
>>> B = np.array([[4, 5],[6, 7]])
>>> B @ A
array([[19, 28],
[27, 40]])

Can a Python class apply two-way overloading?

You could implement __rmul__.

These methods are called to implement the binary arithmetic operations (+, -, *, /, %, divmod(), pow(), **, <<, >>, &, ^, |) with reflected (swapped) operands. These functions are only called if the left operand does not support the corresponding operation and the operands are of different types.

Python: What does it mean when two objects are multiplied together using an asterisk (*)?

The * operator just means "multiply".

But what it means for two objects to be multiplied is up to those objects' types to decide.

For all of the builtin and stdlib types, it means multiplication if that makes sense, or it's a TypeError if that doesn't make sense. For example:

>>> 2 * 3
6
>>> (1, 2, 3) * 3
(1, 2, 3, 1, 2, 3, 1, 2, 3)
>>> "abc" * "def"
TypeError: can't multiply sequence by non-int of type 'str'

Since multiplication doesn't make sense for class objects, they're one of the kinds of things where it's a TypeError:

>>> class C: pass
>>> C * 2
TypeError: unsupported operand type(s) for *: 'type' and 'int'

If you (or a third-party library author) create a new type, you can decide what multiplication means. The way you do this is covered under Emulating numeric types in the documentation, but the short version is that you define a __mul__ method:

class MyNumberyThing:
def __init__(self, n):
self.n = n
def __repr__(self):
return f'MyNumberyType({self.n})'
def __mul__(self, other):
if isinstance(other, MyNumberyThing):
return MyNumberyThing(self.n * other.n)
elif isinstance(other, int):
return MyNumberyThing(self.n * other)
elif isinstance(other, float):
raise TypeError("sorry, can't multiply by float because of precision issues")
else:
raise TypeError(f"sorry, don't know how to multiply by {type(other).__name__}")

Notice that this makes instances of MyNumberyThing multiplicable. It doesn't make MyNumberyThing itself multiplicable (MyNumberyThing isn't a MyNumberyThing, it's a type):

>>> n = MyNumberyThing(2)
>>> n * 3
MyNumberyType(6)
>>> MyNumberyThing * 3
TypeError: unsupported operand type(s) for *: 'type' and 'int'

Of course nothing's stopping you from defining something ridiculous:

class MySillyThing:
def __mul__(self, other):
self.storage = -other
print(f'I took your {other}')

>>> silly = MySillyThing()
>>> silly * 3
I took your 3
>>> silly.storage
-3

… except, of course, for the fact that nobody would understand your code. (And that "nobody" includes you, 6 months later trying to debug something you thought you were done with…)


As a side note, Python actually has two ways to spell "multiply": * and @.

The builtin types all ignore @, so you'll just get a TypeError if you try to use it. What it's there for is, basically, so NumPy can use * for elementwise multiplication, and @ for matrix multiplication. (The @ operator's magic method is even called __matmul__.) But of course other third-party libraries that similarly needed to do two different kinds of multiplication could use @ for the second kind.



Related Topics



Leave a reply



Submit