← Назад к вопросам

Порядок вызова операторов

2.2 Middle🔥 81 комментариев
#Python Core

Условие

Какая последовательность вызова операторов в выражении:

a * b * c

Задача

  1. Объясните порядок вычисления (слева направо или справа налево?)
  2. Какой метод вызывается: mul или rmul?
  3. Что произойдёт, если a.mul(b) вернёт NotImplemented?

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение: Порядок Вызова Операторов в Python

Это глубокий вопрос о том, как Python интерпретирует бинарные операции и вызывает магические методы.

Часть 1: Порядок Вычисления

Выражение a * b * c вычисляется СЛЕВА НАПРАВО:

a * b * c  →  (a * b) * c

Это называется левая ассоциативность (left-to-right associativity).

Процесс:

  1. Вычисляем a * b → результат temp
  2. Вычисляем temp * c → финальный результат
class Custom:
    def __init__(self, value):
        self.value = value
    
    def __mul__(self, other):
        print(f"__mul__: {self.value} * {other.value}")
        return Custom(self.value * other.value)

a = Custom(2)
b = Custom(3)
c = Custom(4)

result = a * b * c

Вывод:

__mul__: 2 * 3
__mul__: 6 * 4

Часть 2: Какой Метод Вызывается?

Для a * b вызывается именно a.__mul__(b)

Порядок попыток:

a * b  →  a.__mul__(b)
        if NotImplemented: b.__rmul__(a)

__mul__ — основной метод (левый операнд) __rmul__ — отражённый метод (вызывается, если левый вернул NotImplemented)

class A:
    def __mul__(self, other):
        print(f"A.__mul__ called")
        return "A mul"

class B:
    def __rmul__(self, other):
        print(f"B.__rmul__ called")
        return "B rmul"

a = A()
b = B()

print(a * b)  # Выведет: A.__mul__ called, результат: A mul
print(b * a)  # Выведет: A.__mul__ called (через __rmul__ B), результат: A mul

Часть 3: Цепочка Разрешения (Resolution Chain)

Если a.__mul__(b) вернёт NotImplemented, Python попробует b.__rmul__(a):

class Vector:
    def __init__(self, value):
        self.value = value
    
    def __mul__(self, other):
        if isinstance(other, Vector):
            return Vector(self.value * other.value)
        return NotImplemented  # Не можем умножать на этот тип
    
    def __rmul__(self, other):
        print("__rmul__ called")
        return Vector(other * self.value)
    
    def __repr__(self):
        return f"Vector({self.value})"

v = Vector(5)
scalar = 3

print(v * scalar)  # v.__mul__(3) → NotImplemented → scalar.__rmul__(v) ❌ (int не имеет __rmul__)
print(scalar * v)  # scalar.__mul__(v) → NotImplemented → v.__rmul__(3) → Vector(15) ✓

Вывод:

__rmul__ called
Vector(15)

Полный Процесс Разрешения

Для выражения x * y:

  1. Python вызывает x.__mul__(y)
  2. Если возвращен результат (не NotImplemented) → вернуть его
  3. Если возвращен NotImplemented → перейти к шагу 4
  4. Python вызывает y.__rmul__(x)
  5. Если возвращен результат (не NotImplemented) → вернуть его
  6. Если возвращен NotImplemented → выбросить TypeError
class A:
    def __mul__(self, other):
        print(f"A.__mul__ called with {type(other).__name__}")
        return NotImplemented

class B:
    def __rmul__(self, other):
        print(f"B.__rmul__ called with {type(other).__name__}")
        return "Success from B"

class C:
    pass

a = A()
b = B()
c = C()

print(a * b)  # A.__mul__, потом B.__rmul__ → "Success from B"
print(a * c)  # A.__mul__, потом C.__rmul__ (не существует) → TypeError

Практический Пример: Матрица и Скаляр

class Matrix:
    def __init__(self, data):
        self.data = data
    
    def __mul__(self, other):
        if isinstance(other, (int, float)):
            return Matrix([[x * other for x in row] for row in self.data])
        return NotImplemented
    
    def __rmul__(self, other):
        # Скалярное умножение коммутативно
        return self.__mul__(other)
    
    def __repr__(self):
        return f"Matrix({self.data})"

m = Matrix([[1, 2], [3, 4]])

print(m * 5)  # Matrix.__mul__(5) → успех
print(5 * m)  # int.__mul__(Matrix) → NotImplemented → Matrix.__rmul__(5) → успех

Ассоциативность Для Разных Операторов

# Все эти операторы имеют ЛЕВУЮ ассоциативность
a + b + c    →  (a + b) + c     # __add__, __radd__
a - b - c    →  (a - b) - c     # __sub__, __rsub__
a * b * c    →  (a * b) * c     # __mul__, __rmul__
a / b / c    →  (a / b) / c     # __truediv__, __rtruediv__
a // b // c  →  (a // b) // c   # __floordiv__, __rfloordiv__
a ** b ** c  →  a ** (b ** c)   # __pow__, __rpow__ — СПРАВА НАЛЕВО!

Рекомендация

Для собеседования:

  1. Порядок вычисления: слева направо для a * b * c(a * b) * c
  2. Метод: сначала a.__mul__(b), потом если NotImplemented → b.__rmul__(a)
  3. Цепочка: Python следует алгоритму разрешения, пока не найдёт работающий метод или не выбросит TypeError

Это показывает глубокое понимание протокола методов Python и того, как язык обрабатывает операции.