operadores - python operador diferente de
Sobrecarga del operador en Python: manejo de diferentes tipos y orden de parámetros (2)
Esta pregunta ya tiene una respuesta aquí:
- El operador conmutativo Python anula 1 respuesta
Tengo una clase simple que ayuda con operaciones matemáticas en vectores (es decir, listas de números). Mi Vector se puede multiplicar por otras instancias de Vector o un escalar ( float o int ).
En otros lenguajes más fuertemente tipados, crearía un método para multiplicar dos vector y un método separado para multiplicar un vector por y int / float . Todavía soy bastante nuevo en Python y no estoy seguro de cómo implementarlo. La única forma en que puedo pensar en hacerlo es anular __mul__() y probar el parámetro entrante:
class Vector(object):
...
def __mul__(self, rhs):
if isinstance(rhs, Vector):
...
if isinstance(rhs, int) or isinstance(rhs, float):
...
Incluso si lo hago de esa manera, me veré obligado a multiplicar un Vector por un escalar como este:
v = Vector([1,2,3])
result = v * 7
¿Qué sucede si quiero invertir el orden de los operandos en la multiplicación?
result = 7 * v
¿Cuál es la forma correcta de hacerlo en Python?
Hay métodos especiales para operaciones inversas :
-
__rmul__por el reverso de__mul__ - y
__radd__por__add__, - ...
Se NotImplemented cuando el operador del lado izquierdo devuelve NotImplemented para la operación normal (por lo que la operación 2 + vector_instance probará primero: (2).__add__(vector_instance) pero si esto devuelve NotImplemented entonces se vector_instance.__radd__(2) ).
Sin embargo, no utilizaría isinstance comprobaciones de isinstance en los métodos aritméticos especiales, que darán lugar a mucha repetición de código.
En realidad, podría crear un caso especial en __init__ e implementar una conversión de escalares a un Vector allí:
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)
Esto debería funcionar como se esperaba:
>>> 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)
Tenga en cuenta que este fue un borrador rápido y sucio (que podría tener varios errores). Solo quería presentar la "idea general" de cómo podría resolverse sin una carcasa especial del tipo en cada operación aritmética.
También necesita implementar __rmul__ . Cuando falla la llamada inicial a int.__mul__(7, v) , Python intentará luego con el type(v).__rmul__(v, 7) .
def __rmul__(self, lhs):
return self * lhs # Effectively, turn 7 * v into v * 7
Como señala Rawing, simplemente podría escribir __rmul__ = __mul__ para esta definición. __rmul__ existe para permitir la multiplicación no conmutativa en la que simplemente diferir a __mul__ con los operandos invertidos no es suficiente.
Por ejemplo, si estaba escribiendo una clase de Matrix y deseaba apoyar la multiplicación por una lista anidada, por ejemplo,
m = Matrix(...) # Some 2 x 2 matrix
n = [[1, 2], [3,4]]
p = n * m
Aquí, la clase de list no sabría cómo hacer una lista múltiple por una instancia de Matrix , así que cuando la list.__mul__(n, m) falla, Python probaría Matrix.__rmul__(m, n) . Sin embargo, n * m y m * n son dos resultados diferentes en general, entonces Matrix.__rmul__(m, n) != Matrix.__mul__(m, n) ; __rmul__ tiene que hacer un poco de trabajo extra para generar la respuesta correcta.