问题
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?
回答1:
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.
回答2:
There are special methods for reversed operations:
__rmul__
for the reverse of__mul__
- and
__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.
来源:https://stackoverflow.com/questions/44521074/operator-overloading-in-python-handling-different-types-and-order-of-parameters