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.