Перегрузка оператора в Python: обработка различных типов и порядок параметров
У меня есть простой класс, который помогает с математическими операциями над векторами (т.е. списками чисел). Мой Vector
может быть умножен на другие экземпляры Vector
или скаляр (float
или int
).
В других, более строго типизированных языках я бы создал метод для умножения двух Vector
и отдельного метода для умножения a Vector
на и int
/float
. Я все еще довольно новичок в Python и не знаю, как это реализовать. Единственный способ, которым я могу это сделать, - переопределить __mul__()
и проверить входящий параметр:
class Vector(object):
...
def __mul__(self, rhs):
if isinstance(rhs, Vector):
...
if isinstance(rhs, int) or isinstance(rhs, float):
...
Даже если я это сделаю, мне пришлось бы умножить Vector
на такой скаляр:
v = Vector([1,2,3])
result = v * 7
Что делать, если я хотел бы изменить порядок операндов при умножении?
result = 7 * v
Каков правильный способ сделать это в Python?
Ответы
Ответ 1
Вам также необходимо реализовать __rmul__
. Когда начальный вызов int.__mul__(7, v)
завершается с ошибкой, Python попытается выполнить следующую попытку type(v).__rmul__(v, 7)
.
def __rmul__(self, lhs):
return self * lhs # Effectively, turn 7 * v into v * 7
Как указывает Rawing, вы можете просто написать __rmul__ = __mul__
для этого определения. __rmul__
существует, чтобы допускать некоммутативное умножение, когда просто отложить до __mul__
с обратными операндами недостаточно.
Например, если вы пишете класс Matrix
и хотите поддерживать умножение вложенным списком, например,
m = Matrix(...) # Some 2 x 2 matrix
n = [[1, 2], [3,4]]
p = n * m
Здесь класс list
не знал бы, как многократный список экземпляром Matrix
, поэтому, когда list.__mul__(n, m)
не работает, Python попытается попробовать Matrix.__rmul__(m, n)
. Однако n * m
и m * n
представляют собой два разных результата в целом, поэтому Matrix.__rmul__(m, n) != Matrix.__mul__(m, n)
; __rmul__
должен сделать небольшую дополнительную работу для получения правильного ответа.
Ответ 2
специальные методы для обратных операций:
-
__rmul__
для обратного __mul__
- и
__radd__
для __add__
,
- ...
Они вызывается, когда оператор левой стороны возвращает NotImplemented
для нормальной работы (поэтому сначала будет выполняться операция 2 + vector_instance
: (2).__add__(vector_instance)
, но если это возвращает NotImplemented
, то вызывается vector_instance.__radd__(2)
).
Однако я не использовал бы теги isinstance
в арифметических специальных методах, что приведет к много повторения кода.
Фактически вы можете создать специальный случай в __init__
и реализовать преобразование из скаляров в Vector
там:
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)
Это должно работать как ожидалось:
>>> 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)
Обратите внимание, что это был быстрый и грязный проект (который может содержать несколько ошибок). Я просто хотел представить "общую идею" о том, как ее можно было бы решить без специального обтекания типа в каждой арифметической операции.