2011-03-03 12 views

Respuesta

83

Cuando Python intenta multiplicar dos objetos, primero intenta llamar al método __mul__() del objeto izquierdo. Si el objeto izquierdo no tiene un método __mul__() (o el método devuelve NotImpemented, lo que indica que no funciona con el operando correcto en cuestión), Python quiere saber si el objeto correcto puede hacer la multiplicación. Si el operando correcto es del mismo tipo que el izquierdo, Python sabe que no puede, porque si el objeto izquierdo no puede hacerlo, tampoco puede hacerlo otro objeto del mismo tipo.

Si los dos objetos son de tipos diferentes, sin embargo, Python cree que vale la pena intentarlo. Sin embargo, necesita alguna forma de decirle al objeto correcto que es el objeto correcto en la operación, en caso de que la operación no sea conmutativa. (La multiplicación es, por supuesto, pero no todos los operadores lo son, y en cualquier caso * no siempre se usa para la multiplicación). Por lo tanto, llama a __rmul__() en lugar de __mul__().

Como ejemplo, considere las siguientes dos declaraciones:

print "nom" * 3 
print 3 * "nom" 

En el primer caso, Python invocará el método de la cadena __mul__(). La cadena sabe cómo multiplicarse por un número entero, así que todo está bien. En el segundo caso, el entero no sabe cómo multiplicarse por una cadena, por lo que su __mul()__ devuelve NotImplemented y se llama a la cadena __rmul()__. Sabe qué hacer y obtiene el mismo resultado que el primer caso.

Ahora podemos ver que __rmul()__ permite todo del comportamiento de la multiplicación especial de la cadena para estar contenido en la clase str, de tal manera que otros tipos (como números enteros) no necesitan saber nada acerca de las cadenas que ser capaz de multiplicar por ellos. Dentro de cien años (suponiendo que Python todavía esté en uso) podrá definir un nuevo tipo que se puede multiplicar por un entero en cualquier orden, aunque la clase int no ha sabido nada de eso durante más de un siglo.

Por cierto, la clase de cadena __mul__() tiene un error en algunas versiones de Python. Si no sabe cómo multiplicarse por un objeto, aumenta un TypeError en lugar de devolver NotImplemented.Eso significa que no puede multiplicar una cadena por un tipo definido por el usuario, incluso si el tipo definido por el usuario tiene un método __rmul__(), porque la cadena nunca deja que tenga una oportunidad. El tipo definido por el usuario debe ir primero (por ejemplo, Foo() * 'bar' en lugar de 'bar' * Foo()) por lo que se llama al __mul__(). Parece que arreglaron esto en Python 2.7 (lo probé en Python 3.2 también), pero Python 2.6.6 tiene el error.


+2

No hay nada malo con la respuesta aceptada anteriormente, pero si alguien aparece aquí en el futuro, probablemente quieran encontrar esta respuesta en la parte superior, así que lo aceptaré. – porgarmingduod

+1

++, excelente respuesta - Aprendí algo :) –

+0

kindall dijo: Probablemente este sea un esfuerzo perdido ahora que has aceptado una respuesta, pero: Quiero asegurarte que tu esfuerzo no se desperdició - Tuve un problema con permitiendo el uso de __rmul__ en el álgebra vectorial (para la multiplicación escalar de vectores). Tu explicación fue suficiente para convencerme de que en el caso de una operación (escalar) * (vector) el método __mul__ debería terminar con "raise NotImplementedError()" o "return Not Implemented" para permitir que una llamada vaya al método __rmul__. ¡Gracias por tu ayuda! – user377367

7

Los operadores binarios, por su naturaleza, tienen dos operandos. Cada operando puede estar en el lado izquierdo o derecho de un operador. Cuando sobrecarga un operador por algún tipo, puede especificar para qué lado del operador se realiza la sobrecarga. Esto es útil cuando se invoca al operador en dos operandos de diferentes tipos. He aquí un ejemplo:

class Foo(object): 
    def __init__(self, val): 
     self.val = val 

    def __str__(self): 
     return "Foo [%s]" % self.val 


class Bar(object): 
    def __init__(self, val): 
     self.val = val 

    def __rmul__(self, other): 
     return Bar(self.val * other.val) 

    def __str__(self): 
     return "Bar [%s]" % self.val 


f = Foo(4) 
b = Bar(6) 

obj = f * b # Bar [24] 
obj2 = b * f # ERROR 

aquí, obj habrá un Bar con val = 24, pero la asignación a obj2 genera un error porque no tiene Bar__mul__ y Foo no tiene __rmul__.

Espero que esto sea lo suficientemente claro.

Cuestiones relacionadas