При каких обстоятельствах __rmul__ называется?

Скажем, у меня есть список l. При каких обстоятельствах l.__rmul__(self, other) называется?

Я в основном понял документацию, но я также хотел бы увидеть пример, чтобы прояснить его использование вне всяких сомнений.

Ответ 1

Когда Python пытается умножить два объекта, он сначала пытается вызвать метод __mul__() левого объекта. Если левый объект не имеет __mul__() (или метод возвращает NotImplemented, указывая, что он не работает с соответствующим операндом), то Python хочет знать, может ли правильный объект выполнить умножение. Если правый операнд того же типа, что и левый, Python знает, что это невозможно, потому что, если левый объект не может этого сделать, другой объект того же типа, безусловно, тоже не может.

Если два объекта разных типов, то Python считает, что это стоит попробовать. Однако ему нужен какой-то способ сообщить нужному объекту, что он является правильным объектом в операции, если операция не является коммутативной. (Умножение, конечно, но не все операторы, и в любом случае * не всегда используется для умножения!) Поэтому он вызывает __rmul__() вместо __mul__().

В качестве примера рассмотрим следующие два утверждения:

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

В первом случае Python вызывает метод string __mul__(). Строка знает, как умножить себя на целое число, так что все хорошо. Во втором случае целое число не знает, как умножить себя на строку, поэтому его __mul()__ возвращает NotImplemented и __rmul()__ строка __rmul()__. Он знает, что делать, и вы получите тот же результат, что и в первом случае.

Теперь мы можем видеть, что __rmul()__ позволяет содержать все особенности специального умножения строк в классе str, так что другим типам (таким как целые числа) не нужно ничего знать о строках, чтобы иметь возможность их умножать на них. Через сто лет (при условии, что Python все еще используется) вы сможете определить новый тип, который можно умножить на целое число в любом порядке, даже если класс int ничего не знал о нем более столетия.

Кстати, строковый класс __mul__() имеет ошибку в некоторых версиях Python. Если он не знает, как умножить себя на объект, он вызывает TypeError вместо возврата NotImplemented. Это означает, что вы не можете умножить строку на определенный пользователем тип, даже если этот определенный тип имеет __rmul__(), потому что строка никогда не дает ей шанс. Пользовательский тип должен идти первым (например, Foo() * 'bar' вместо 'bar' * Foo()), поэтому __mul__() его __mul__(). Кажется, они исправили это в Python 2.7 (я проверял это и в Python 3.2), но в Python 2.6.6 есть ошибка.


Ответ 2

Двоичные операторы по своей природе имеют два операнда. Каждый операнд может находиться либо на левой, либо на правой стороне оператора. Когда вы перегружаете оператор для некоторого типа, вы можете указать, на какой стороне оператора выполняется перегрузка. Это полезно при вызове оператора на двух операндах разных типов. Вот пример:

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

Здесь obj будет Bar с val = 24, но присваивание obj2 генерирует ошибку, потому что Bar не имеет __mul__ и Foo не имеет __rmul__.

Надеюсь, это достаточно ясно.