I have a simple class that helps with mathematical operations on vectors (i.e. lists of numbers). My Vector
can be multiplied by other instances of Vector
or a scalar (float
or int
).
In other, more strongly typed, languages I would create a method to multiply two vector
s and a separate method to multiply a vector
by and int
/float
. I'm still pretty new to Python and am not sure how I would implement this. The only way I can think of doing it is override __mul__()
and test the incoming parameter:
class Vector(object):
...
def __mul__(self, rhs):
if isinstance(rhs, Vector):
...
if isinstance(rhs, int) or isinstance(rhs, float):
...
Even if I do it that way I would be forced to multiply a Vector
by a scalar like this:
v = Vector([1,2,3])
result = v * 7
What if I wanted to reverse the order of the operands in the multiplication?
result = 7 * v
What is the right way to do that in Python?
There isn't any method overloading in Python. You can however use default arguments, as follows. When you pass it an argument, it will follow the logic of the first condition and execute the first print statement. When you pass it no arguments, it will go into the else condition and execute the second print statement.
Operator function must be either non-static (member function) or friend function. Overloading unary operator. Overloading binary operator. Overloading binary operator using a friend function.
There are special methods for reversed operations:
__rmul__
for the reverse of __mul__
__radd__
for __add__
,These are called when the left hand side operator returns NotImplemented
for the normal operation (so the operation 2 + vector_instance
will first try: (2).__add__(vector_instance)
but if this returns NotImplemented
then vector_instance.__radd__(2)
is called).
However I wouldn't use isinstance
checks in the arithmetic special methods, that will lead to a lot of code repetition.
You could actually create a special case in __init__
and implement a conversion from scalars to a Vector
there:
class Vector(object):
def __init__(self, x, y=None, z=None):
if y is None and z is None:
if isinstance(x, Vector):
self.x, self.y, self.z = x.x, x.y, x.z
else:
self.x, self.y, self.z = x, x, x
elif y is None or z is None:
raise ValueError('Either x, y and z must be given or only x')
else:
self.x, self.y, self.z = x, y, z
def __mul__(self, other):
other = Vector(other)
return Vector(self.x*other.x, self.y*other.y, self.z*other.z)
__rmul__ = __mul__ # commutative operation
def __sub__(self, other):
other = Vector(other)
return Vector(self.x-other.x, self.y-other.y, self.z-other.z)
def __rsub__(self, other): # not commutative operation
other = Vector(other)
return other - self
def __repr__(self):
return 'Vector({self.x}, {self.y}, {self.z})'.format(self=self)
This should work as expected:
>>> 2 - Vector(1, 2, 3)
Vector(1, 0, -1)
>>> Vector(1, 2, 3) - 2
Vector(-1, 0, 1)
>>> Vector(1, 2, 3) * 2
Vector(2, 4, 6)
>>> 2 * Vector(1, 2, 3)
Vector(2, 4, 6)
Note that this was a quick and dirty draft (that could have several bugs). I just wanted to present the "general idea" how it could be solved without special casing the type in each arithmetic operation.
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.
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