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 ofotherclass = 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
Does Conda Replace the Need for Virtualenv
Scipy Curve_Fit Doesn't Like Math Module
What Does Model.Train() Do in Pytorch
Where Do the Python Unit Tests Go
Best Way to Make Django's Login_Required the Default
How to Frame Two for Loops in List Comprehension Python
Brew Installation of Python 3.6.1: [Ssl: Certificate_Verify_Failed] Certificate Verify Failed
Running Jupyter with Multiple Python and Ipython Paths
Determine Prefix from a Set of (Similar) Strings
How to Troubleshoot Python "Could Not Find Platform Independent Libraries <Prefix>"
Find Index of Last Occurrence of a Substring in a String
Listing Contents of a Bucket with Boto3
Python - Datetime with Timezone to Epoch
Overriding the Save Method in Django Modelform
Pandas - Add New Column to Dataframe from Dictionary