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

北城余情 提交于 2020-04-08 00:50:08

问题


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 vectors 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!